/*-------------------------------------------------------------------------
 *
 * execExprInterp.c
 *	  表达式步骤列表的解释评估。
 *
 * 本文件提供了“直接线程”（适用于 gcc、clang 和
 * 兼容的）的实现或“switch 线程”（适用于所有编译器）的表达式评估实现。
 * 前者是已知最快速的解释程序方法之一，无需诉诸汇编级工作或
 * 即时编译，但它需要对计算跳转的支持。后者是在标准 C 中可实现的最快方法之一。
 *
 * 在这两种情况下，我们使用 ExprEvalStep->opcode 来调度到
 * ExecInterpExpr() 中实现特定操作码类型的代码块。
 *
 * Switch-threading 使用普通的 switch() 语句进行调度。这的优点是使用普通的 C 并允许编译器
 * 发出警告，以防某个特定操作码的实现被遗忘。缺点是调度通常会由
 * 编译器从单个位置实现，需要更多跳转并导致较差的分支预测。
 *
 * 在直接线程中，我们使用 gcc 的 label-as-values 扩展 - 也被一些其他编译器接受 - 用于用实现指令的代码块地址替换
 * ExprEvalStep->opcode。通过“计算跳转”来调度到下一条指令。这可以实现更好的分支预测
 * （因为跳转发生在不同位置）和更少的跳转（因为不需要准备跳转到公共调度位置）。
 *
 * 在使用直接线程时，ExecReadyInterpretedExpr 将用相关代码块的地址替换每个步骤的操作码字段，
 * 并且 ExprState->flags 将包含 EEO_FLAG_DIRECT_THREADED，以记住已执行的操作。
 *
 * 对于非常简单的指令，完整的解释器“启动”开销，尽管最小，但仍然是显著的。因此
 * ExecReadyInterpretedExpr 将选择使用特定的快速路径例程（ExecJust*）来实现某些简单
 * 操作码模式。
 *
 * 复杂或不常见的指令不会在 ExecInterpExpr() 中行内实现，而是调用在本文件后面出现的辅助函数。原因之一是
 * 没有显著的性能收益，更重要的是这些复杂例程旨在在不同的表达式评估方法之间共享。例如，
 * JIT 编译器将生成对它们的调用。这就是为什么它们被导出而不是在本文件中“静态”的原因。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/executor/execExprInterp.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/heaptoast.h"
#include "catalog/pg_type.h"
#include "commands/sequence.h"
#include "executor/execExpr.h"
#include "executor/nodeSubplan.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "parser/parsetree.h"
#include "pgstat.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/date.h"
#include "utils/datum.h"
#include "utils/expandedrecord.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/timestamp.h"
#include "utils/typcache.h"
#include "utils/xml.h"

/*
 * 当可用计算 goto 时使用基于计算 goto 的操作码调度。
 * 但使用一个单独的符号，以便在此文件中本地调整以进行开发和测试。
 */
#ifdef HAVE_COMPUTED_GOTO
#define EEO_USE_COMPUTED_GOTO
#endif							/* HAVE_COMPUTED_GOTO */

/*
 * 操作码调度的宏。
 *
 * EEO_SWITCH - 如果不使用则隐藏开关。
 * EEO_CASE - 标记命名表达步类型的实现。
 * EEO_DISPATCH - 跳转到'op'的步类型实现。
 * EEO_OPCODE - 计算所需的操作码，根据使用的表达式评价方法。
 * EEO_NEXT - 增加'op'并跳转到正确的下一个步骤类型。
 * EEO_JUMP - 在当前表达式内跳转到指定的步骤号。
 */
#if defined(EEO_USE_COMPUTED_GOTO)

/* 跳转目标 -> 操作码查找表的结构 */
typedef struct ExprEvalOpLookup
{
	const void *opcode;
	ExprEvalOp	op;
} ExprEvalOpLookup;

/* 使dispatch_table能够在ExecInterpExpr()之外访问 */
static const void **dispatch_table = NULL;

/* 跳转目标 -> 操作码查找表 */
static ExprEvalOpLookup reverse_dispatch_table[EEOP_LAST];

#define EEO_SWITCH()
#define EEO_CASE(name)		CASE_##name:
#define EEO_DISPATCH()		goto *((void *) fc_op->opcode)
#define EEO_OPCODE(opcode)	((intptr_t) dispatch_table[opcode])

#else							/* !EEO_USE_COMPUTED_GOTO */

#define EEO_SWITCH()		starteval: switch ((ExprEvalOp) op->opcode)
#define EEO_CASE(name)		case name:
#define EEO_DISPATCH()		goto starteval
#define EEO_OPCODE(opcode)	(opcode)

#endif							/* EEO_USE_COMPUTED_GOTO */

#define EEO_NEXT() \
	do { \
		fc_op++; \
		EEO_DISPATCH(); \
	} while (0)

#define EEO_JUMP(stepno) \
	do { \
		fc_op = &fc_state->steps[stepno]; \
		EEO_DISPATCH(); \
	} while (0)


static Datum fc_ExecInterpExpr(ExprState *fc_state, ExprContext *fc_econtext, bool *fc_isnull);
static void fc_ExecInitInterpreter(void);

/* 支持函数 */
static void fc_CheckVarSlotCompatibility(TupleTableSlot *fc_slot, int fc_attnum, Oid fc_vartype);
static void fc_CheckOpSlotCompatibility(ExprEvalStep *fc_op, TupleTableSlot *fc_slot);
static TupleDesc get_cached_rowtype(Oid fc_type_id, int32 fc_typmod,
									ExprEvalRowtypeCache *fc_rowcache,
									bool *fc_changed);
static void fc_ExecEvalRowNullInt(ExprState *fc_state, ExprEvalStep *fc_op,
							   ExprContext *fc_econtext, bool fc_checkisnull);

/* 快速路径评估函数 */
static Datum fc_ExecJustInnerVar(ExprState *fc_state, ExprContext *fc_econtext, bool *fc_isnull);
static Datum fc_ExecJustOuterVar(ExprState *fc_state, ExprContext *fc_econtext, bool *fc_isnull);
static Datum fc_ExecJustScanVar(ExprState *fc_state, ExprContext *fc_econtext, bool *fc_isnull);
static Datum fc_ExecJustAssignInnerVar(ExprState *fc_state, ExprContext *fc_econtext, bool *fc_isnull);
static Datum fc_ExecJustAssignOuterVar(ExprState *fc_state, ExprContext *fc_econtext, bool *fc_isnull);
static Datum fc_ExecJustAssignScanVar(ExprState *fc_state, ExprContext *fc_econtext, bool *fc_isnull);
static Datum fc_ExecJustApplyFuncToCase(ExprState *fc_state, ExprContext *fc_econtext, bool *fc_isnull);
static Datum fc_ExecJustConst(ExprState *fc_state, ExprContext *fc_econtext, bool *fc_isnull);
static Datum fc_ExecJustInnerVarVirt(ExprState *fc_state, ExprContext *fc_econtext, bool *fc_isnull);
static Datum fc_ExecJustOuterVarVirt(ExprState *fc_state, ExprContext *fc_econtext, bool *fc_isnull);
static Datum fc_ExecJustScanVarVirt(ExprState *fc_state, ExprContext *fc_econtext, bool *fc_isnull);
static Datum fc_ExecJustAssignInnerVarVirt(ExprState *fc_state, ExprContext *fc_econtext, bool *fc_isnull);
static Datum fc_ExecJustAssignOuterVarVirt(ExprState *fc_state, ExprContext *fc_econtext, bool *fc_isnull);
static Datum fc_ExecJustAssignScanVarVirt(ExprState *fc_state, ExprContext *fc_econtext, bool *fc_isnull);

/* 执行辅助函数 */
static pg_attribute_always_inline void fc_ExecAggPlainTransByVal(AggState *fc_aggstate,
															  AggStatePerTrans fc_pertrans,
															  AggStatePerGroup fc_pergroup,
															  ExprContext *fc_aggcontext,
															  int fc_setno);
static pg_attribute_always_inline void fc_ExecAggPlainTransByRef(AggState *fc_aggstate,
															  AggStatePerTrans fc_pertrans,
															  AggStatePerGroup fc_pergroup,
															  ExprContext *fc_aggcontext,
															  int fc_setno);

/*
 * ScalarArrayOpExprHashEntry
 * 		用于EEOP_HASHED_SCALARARRAYOP的哈希表条目类型
 */
typedef struct ScalarArrayOpExprHashEntry
{
	Datum		key;
	uint32		status;			/* 哈希状态 */
	uint32		hash;			/* 哈希值（缓存） */
} ScalarArrayOpExprHashEntry;

#define SH_PREFIX saophash
#define SH_ELEMENT_TYPE ScalarArrayOpExprHashEntry
#define SH_KEY_TYPE Datum
#define SH_SCOPE static inline
#define SH_DECLARE
#include "lib/simplehash.h"

static bool fc_saop_hash_element_match(struct saophash_hash *fc_tb, Datum fc_key1,
									Datum fc_key2);
static uint32 fc_saop_element_hash(struct saophash_hash *fc_tb, Datum fc_key);

/*
 * ScalarArrayOpExprHashTable
 *		用于EEOP_HASHED_SCALARARRAYOP的哈希表
 */
typedef struct ScalarArrayOpExprHashTable
{
	saophash_hash *hashtab;		/* 底层哈希表 */
	struct ExprEvalStep *op;
	FmgrInfo	hash_finfo;		/* 函数的查找数据 */
	FunctionCallInfoBaseData hash_fcinfo_data;	/* 参数等 */
} ScalarArrayOpExprHashTable;

/* 定义用于ScalarArrayOpExpr哈希表代码生成的参数。 */
#define SH_PREFIX saophash
#define SH_ELEMENT_TYPE ScalarArrayOpExprHashEntry
#define SH_KEY_TYPE Datum
#define SH_KEY key
#define SH_HASH_KEY(tb, key) fc_saop_element_hash(tb, key)
#define SH_EQUAL(tb, a, b) fc_saop_hash_element_match(tb, a, b)
#define SH_SCOPE static inline
#define SH_STORE_HASH
#define SH_GET_HASH(tb, a) a->hash
#define SH_DEFINE
#include "lib/simplehash.h"

/*
 * 准备ExprState以供解释执行。
 */
void ExecReadyInterpretedExpr(ExprState *fc_state)
{
	/* 确保一次性的解释器设置已经完成 */
	fc_ExecInitInterpreter();

	/* 对表达式进行简单的有效性检查 */
	Assert(fc_state->steps_len >= 1);
	Assert(fc_state->steps[fc_state->steps_len - 1].opcode == EEOP_DONE);

	/*
	 * 不执行冗余初始化。在当前情况下这是不可达的，
	 * 但如果有依赖于解释执行的其他表达式评估方法可能会出现。
	 */
	if (fc_state->flags & EEO_FLAG_INTERPRETER_INITIALIZED)
		return;

	/*
	 * 第一次运行时，检查属性是否与Var匹配。由于模式更改，
	 * 这可能不再可行。我们通过设置一个回调来完成这一点，
	 * 在第一次调用时进行检查，然后将evalfunc回调
	 * 设置为实际的执行方法。
	 */
	fc_state->evalfunc = ExecInterpExprStillValid;

	/* DIRECT_THREADED 不应已经设置 */
	Assert((fc_state->flags & EEO_FLAG_DIRECT_THREADED) == 0);

	/*
	 * 在表达式完全初始化之前不应该有任何错误，
	 * 即使有，也会导致表达式被放弃。 所以我们现在可以
	 * 设置标志，从而节省一些代码。
	 */
	fc_state->flags |= EEO_FLAG_INTERPRETER_INITIALIZED;

	/*
	 * 为非常简单的表达式选择快速路径评估函数。“启动”
	 * 完整的解释器对这些来说是可测量的开销，并且这些
	 * 模式经常出现，因此值得优化。
	 */
	if (fc_state->steps_len == 3)
	{
		ExprEvalOp	fc_step0 = fc_state->steps[0].opcode;
		ExprEvalOp	fc_step1 = fc_state->steps[1].opcode;

		if (fc_step0 == EEOP_INNER_FETCHSOME &&
			fc_step1 == EEOP_INNER_VAR)
		{
			fc_state->evalfunc_private = (void *) fc_ExecJustInnerVar;
			return;
		}
		else if (fc_step0 == EEOP_OUTER_FETCHSOME &&
				 fc_step1 == EEOP_OUTER_VAR)
		{
			fc_state->evalfunc_private = (void *) fc_ExecJustOuterVar;
			return;
		}
		else if (fc_step0 == EEOP_SCAN_FETCHSOME &&
				 fc_step1 == EEOP_SCAN_VAR)
		{
			fc_state->evalfunc_private = (void *) fc_ExecJustScanVar;
			return;
		}
		else if (fc_step0 == EEOP_INNER_FETCHSOME &&
				 fc_step1 == EEOP_ASSIGN_INNER_VAR)
		{
			fc_state->evalfunc_private = (void *) fc_ExecJustAssignInnerVar;
			return;
		}
		else if (fc_step0 == EEOP_OUTER_FETCHSOME &&
				 fc_step1 == EEOP_ASSIGN_OUTER_VAR)
		{
			fc_state->evalfunc_private = (void *) fc_ExecJustAssignOuterVar;
			return;
		}
		else if (fc_step0 == EEOP_SCAN_FETCHSOME &&
				 fc_step1 == EEOP_ASSIGN_SCAN_VAR)
		{
			fc_state->evalfunc_private = (void *) fc_ExecJustAssignScanVar;
			return;
		}
		else if (fc_step0 == EEOP_CASE_TESTVAL &&
				 fc_step1 == EEOP_FUNCEXPR_STRICT &&
				 fc_state->steps[0].d.casetest.value)
		{
			fc_state->evalfunc_private = (void *) fc_ExecJustApplyFuncToCase;
			return;
		}
	}
	else if (fc_state->steps_len == 2)
	{
		ExprEvalOp	fc_step0 = fc_state->steps[0].opcode;

		if (fc_step0 == EEOP_CONST)
		{
			fc_state->evalfunc_private = (void *) fc_ExecJustConst;
			return;
		}
		else if (fc_step0 == EEOP_INNER_VAR)
		{
			fc_state->evalfunc_private = (void *) fc_ExecJustInnerVarVirt;
			return;
		}
		else if (fc_step0 == EEOP_OUTER_VAR)
		{
			fc_state->evalfunc_private = (void *) fc_ExecJustOuterVarVirt;
			return;
		}
		else if (fc_step0 == EEOP_SCAN_VAR)
		{
			fc_state->evalfunc_private = (void *) fc_ExecJustScanVarVirt;
			return;
		}
		else if (fc_step0 == EEOP_ASSIGN_INNER_VAR)
		{
			fc_state->evalfunc_private = (void *) fc_ExecJustAssignInnerVarVirt;
			return;
		}
		else if (fc_step0 == EEOP_ASSIGN_OUTER_VAR)
		{
			fc_state->evalfunc_private = (void *) fc_ExecJustAssignOuterVarVirt;
			return;
		}
		else if (fc_step0 == EEOP_ASSIGN_SCAN_VAR)
		{
			fc_state->evalfunc_private = (void *) fc_ExecJustAssignScanVarVirt;
			return;
		}
	}

#if defined(EEO_USE_COMPUTED_GOTO)

	/*
	 * 在直接线程实现中，用跳转地址替换每个操作码。
	 * （使用ExecEvalStepOp()获取回操作码。）
	 */
	for (int fc_off = 0; fc_off < fc_state->steps_len; fc_off++)
	{
		ExprEvalStep *fc_op = &fc_state->steps[fc_off];

		fc_op->opcode = EEO_OPCODE(fc_op->opcode);
	}

	fc_state->flags |= EEO_FLAG_DIRECT_THREADED;
#endif							/* EEO_USE_COMPUTED_GOTO */

	fc_state->evalfunc_private = (void *) fc_ExecInterpExpr;
}


/*
 * 在由“econtext”给定的执行上下文中评估由“state”
 * 标识的表达式。*isnull被设置为结果的null标志，
 * Datum值是函数结果。
 *
 * 特殊情况下，如果state为NULL，返回调度表的地址。
 * 这被ExecInitInterpreter用于设置dispatch_table全局变量。
 * （仅在定义了EEO_USE_COMPUTED_GOTO时适用。）
 */
static Datum fc_ExecInterpExpr(ExprState *fc_state, ExprContext *fc_econtext, bool *fc_isnull)
{
	ExprEvalStep *fc_op;
	TupleTableSlot *fc_resultslot;
	TupleTableSlot *fc_innerslot;
	TupleTableSlot *fc_outerslot;
	TupleTableSlot *fc_scanslot;

	/*
	 * 该数组必须与枚举ExprEvalOp的顺序相同。
	 */
#if defined(EEO_USE_COMPUTED_GOTO)
	static const void *const fc_dispatch_table[] = {
		&&CASE_EEOP_DONE,
		&&CASE_EEOP_INNER_FETCHSOME,
		&&CASE_EEOP_OUTER_FETCHSOME,
		&&CASE_EEOP_SCAN_FETCHSOME,
		&&CASE_EEOP_INNER_VAR,
		&&CASE_EEOP_OUTER_VAR,
		&&CASE_EEOP_SCAN_VAR,
		&&CASE_EEOP_INNER_SYSVAR,
		&&CASE_EEOP_OUTER_SYSVAR,
		&&CASE_EEOP_SCAN_SYSVAR,
		&&CASE_EEOP_WHOLEROW,
		&&CASE_EEOP_ASSIGN_INNER_VAR,
		&&CASE_EEOP_ASSIGN_OUTER_VAR,
		&&CASE_EEOP_ASSIGN_SCAN_VAR,
		&&CASE_EEOP_ASSIGN_TMP,
		&&CASE_EEOP_ASSIGN_TMP_MAKE_RO,
		&&CASE_EEOP_CONST,
		&&CASE_EEOP_FUNCEXPR,
		&&CASE_EEOP_FUNCEXPR_STRICT,
		&&CASE_EEOP_FUNCEXPR_FUSAGE,
		&&CASE_EEOP_FUNCEXPR_STRICT_FUSAGE,
		&&CASE_EEOP_BOOL_AND_STEP_FIRST,
		&&CASE_EEOP_BOOL_AND_STEP,
		&&CASE_EEOP_BOOL_AND_STEP_LAST,
		&&CASE_EEOP_BOOL_OR_STEP_FIRST,
		&&CASE_EEOP_BOOL_OR_STEP,
		&&CASE_EEOP_BOOL_OR_STEP_LAST,
		&&CASE_EEOP_BOOL_NOT_STEP,
		&&CASE_EEOP_QUAL,
		&&CASE_EEOP_JUMP,
		&&CASE_EEOP_JUMP_IF_NULL,
		&&CASE_EEOP_JUMP_IF_NOT_NULL,
		&&CASE_EEOP_JUMP_IF_NOT_TRUE,
		&&CASE_EEOP_NULLTEST_ISNULL,
		&&CASE_EEOP_NULLTEST_ISNOTNULL,
		&&CASE_EEOP_NULLTEST_ROWISNULL,
		&&CASE_EEOP_NULLTEST_ROWISNOTNULL,
		&&CASE_EEOP_BOOLTEST_IS_TRUE,
		&&CASE_EEOP_BOOLTEST_IS_NOT_TRUE,
		&&CASE_EEOP_BOOLTEST_IS_FALSE,
		&&CASE_EEOP_BOOLTEST_IS_NOT_FALSE,
		&&CASE_EEOP_PARAM_EXEC,
		&&CASE_EEOP_PARAM_EXTERN,
		&&CASE_EEOP_PARAM_CALLBACK,
		&&CASE_EEOP_CASE_TESTVAL,
		&&CASE_EEOP_MAKE_READONLY,
		&&CASE_EEOP_IOCOERCE,
		&&CASE_EEOP_DISTINCT,
		&&CASE_EEOP_NOT_DISTINCT,
		&&CASE_EEOP_NULLIF,
		&&CASE_EEOP_SQLVALUEFUNCTION,
		&&CASE_EEOP_CURRENTOFEXPR,
		&&CASE_EEOP_NEXTVALUEEXPR,
		&&CASE_EEOP_ARRAYEXPR,
		&&CASE_EEOP_ARRAYCOERCE,
		&&CASE_EEOP_ROW,
		&&CASE_EEOP_ROWCOMPARE_STEP,
		&&CASE_EEOP_ROWCOMPARE_FINAL,
		&&CASE_EEOP_MINMAX,
		&&CASE_EEOP_FIELDSELECT,
		&&CASE_EEOP_FIELDSTORE_DEFORM,
		&&CASE_EEOP_FIELDSTORE_FORM,
		&&CASE_EEOP_SBSREF_SUBSCRIPTS,
		&&CASE_EEOP_SBSREF_OLD,
		&&CASE_EEOP_SBSREF_ASSIGN,
		&&CASE_EEOP_SBSREF_FETCH,
		&&CASE_EEOP_DOMAIN_TESTVAL,
		&&CASE_EEOP_DOMAIN_NOTNULL,
		&&CASE_EEOP_DOMAIN_CHECK,
		&&CASE_EEOP_CONVERT_ROWTYPE,
		&&CASE_EEOP_SCALARARRAYOP,
		&&CASE_EEOP_HASHED_SCALARARRAYOP,
		&&CASE_EEOP_XMLEXPR,
		&&CASE_EEOP_AGGREF,
		&&CASE_EEOP_GROUPING_FUNC,
		&&CASE_EEOP_WINDOW_FUNC,
		&&CASE_EEOP_SUBPLAN,
		&&CASE_EEOP_AGG_STRICT_DESERIALIZE,
		&&CASE_EEOP_AGG_DESERIALIZE,
		&&CASE_EEOP_AGG_STRICT_INPUT_CHECK_ARGS,
		&&CASE_EEOP_AGG_STRICT_INPUT_CHECK_NULLS,
		&&CASE_EEOP_AGG_PLAIN_PERGROUP_NULLCHECK,
		&&CASE_EEOP_AGG_PLAIN_TRANS_INIT_STRICT_BYVAL,
		&&CASE_EEOP_AGG_PLAIN_TRANS_STRICT_BYVAL,
		&&CASE_EEOP_AGG_PLAIN_TRANS_BYVAL,
		&&CASE_EEOP_AGG_PLAIN_TRANS_INIT_STRICT_BYREF,
		&&CASE_EEOP_AGG_PLAIN_TRANS_STRICT_BYREF,
		&&CASE_EEOP_AGG_PLAIN_TRANS_BYREF,
		&&CASE_EEOP_AGG_ORDERED_TRANS_DATUM,
		&&CASE_EEOP_AGG_ORDERED_TRANS_TUPLE,
		&&CASE_EEOP_LAST
	};

	StaticAssertStmt(EEOP_LAST + 1 == lengthof(fc_dispatch_table),
					 "dispatch_table out of whack with ExprEvalOp");

	if (unlikely(fc_state == NULL))
		return PointerGetDatum(fc_dispatch_table);
#else
	Assert(state != NULL);
#endif							/* EEO_USE_COMPUTED_GOTO */

	/* 设置状态 */
	fc_op = fc_state->steps;
	fc_resultslot = fc_state->resultslot;
	fc_innerslot = fc_econtext->ecxt_innertuple;
	fc_outerslot = fc_econtext->ecxt_outertuple;
	fc_scanslot = fc_econtext->ecxt_scantuple;

#if defined(EEO_USE_COMPUTED_GOTO)
	EEO_DISPATCH();
#endif

	EEO_SWITCH()
	{
		EEO_CASE(EEOP_DONE)
		{
			goto out;
		}

		EEO_CASE(EEOP_INNER_FETCHSOME)
		{
			fc_CheckOpSlotCompatibility(fc_op, fc_innerslot);

			slot_getsomeattrs(fc_innerslot, fc_op->d.fetch.last_var);

			EEO_NEXT();
		}

		EEO_CASE(EEOP_OUTER_FETCHSOME)
		{
			fc_CheckOpSlotCompatibility(fc_op, fc_outerslot);

			slot_getsomeattrs(fc_outerslot, fc_op->d.fetch.last_var);

			EEO_NEXT();
		}

		EEO_CASE(EEOP_SCAN_FETCHSOME)
		{
			fc_CheckOpSlotCompatibility(fc_op, fc_scanslot);

			slot_getsomeattrs(fc_scanslot, fc_op->d.fetch.last_var);

			EEO_NEXT();
		}

		EEO_CASE(EEOP_INNER_VAR)
		{
			int			fc_attnum = fc_op->d.var.attnum;

			/*
			 * 由于我们已经通过 FETCHSOME 步骤提取了元组中所有引用的列，我们可以直接从槽的分解数据数组中获取值。但让我们有一个断言来检查这一点是否发生。
			 */
			Assert(fc_attnum >= 0 && fc_attnum < fc_innerslot->tts_nvalid);
			*fc_op->resvalue = fc_innerslot->tts_values[fc_attnum];
			*fc_op->resnull = fc_innerslot->tts_isnull[fc_attnum];

			EEO_NEXT();
		}

		EEO_CASE(EEOP_OUTER_VAR)
		{
			int			fc_attnum = fc_op->d.var.attnum;

			/* 请参阅 EEOP_INNER_VAR 注释 */

			Assert(fc_attnum >= 0 && fc_attnum < fc_outerslot->tts_nvalid);
			*fc_op->resvalue = fc_outerslot->tts_values[fc_attnum];
			*fc_op->resnull = fc_outerslot->tts_isnull[fc_attnum];

			EEO_NEXT();
		}

		EEO_CASE(EEOP_SCAN_VAR)
		{
			int			fc_attnum = fc_op->d.var.attnum;

			/* 请参阅 EEOP_INNER_VAR 注释 */

			Assert(fc_attnum >= 0 && fc_attnum < fc_scanslot->tts_nvalid);
			*fc_op->resvalue = fc_scanslot->tts_values[fc_attnum];
			*fc_op->resnull = fc_scanslot->tts_isnull[fc_attnum];

			EEO_NEXT();
		}

		EEO_CASE(EEOP_INNER_SYSVAR)
		{
			ExecEvalSysVar(fc_state, fc_op, fc_econtext, fc_innerslot);
			EEO_NEXT();
		}

		EEO_CASE(EEOP_OUTER_SYSVAR)
		{
			ExecEvalSysVar(fc_state, fc_op, fc_econtext, fc_outerslot);
			EEO_NEXT();
		}

		EEO_CASE(EEOP_SCAN_SYSVAR)
		{
			ExecEvalSysVar(fc_state, fc_op, fc_econtext, fc_scanslot);
			EEO_NEXT();
		}

		EEO_CASE(EEOP_WHOLEROW)
		{
			/* 对内联实现来说太复杂 */
			ExecEvalWholeRowVar(fc_state, fc_op, fc_econtext);

			EEO_NEXT();
		}

		EEO_CASE(EEOP_ASSIGN_INNER_VAR)
		{
			int			fc_resultnum = fc_op->d.assign_var.resultnum;
			int			fc_attnum = fc_op->d.assign_var.attnum;

			/*
			 * 我们在这里不需要 CheckVarSlotCompatibility；这个在编译时已经处理了。但请参阅 EEOP_INNER_VAR 注释。
			 */
			Assert(fc_attnum >= 0 && fc_attnum < fc_innerslot->tts_nvalid);
			Assert(fc_resultnum >= 0 && fc_resultnum < fc_resultslot->tts_tupleDescriptor->natts);
			fc_resultslot->tts_values[fc_resultnum] = fc_innerslot->tts_values[fc_attnum];
			fc_resultslot->tts_isnull[fc_resultnum] = fc_innerslot->tts_isnull[fc_attnum];

			EEO_NEXT();
		}

		EEO_CASE(EEOP_ASSIGN_OUTER_VAR)
		{
			int			fc_resultnum = fc_op->d.assign_var.resultnum;
			int			fc_attnum = fc_op->d.assign_var.attnum;

			/*
			 * 我们在这里不需要 CheckVarSlotCompatibility；这个在编译时已经处理了。但请参阅 EEOP_INNER_VAR 注释。
			 */
			Assert(fc_attnum >= 0 && fc_attnum < fc_outerslot->tts_nvalid);
			Assert(fc_resultnum >= 0 && fc_resultnum < fc_resultslot->tts_tupleDescriptor->natts);
			fc_resultslot->tts_values[fc_resultnum] = fc_outerslot->tts_values[fc_attnum];
			fc_resultslot->tts_isnull[fc_resultnum] = fc_outerslot->tts_isnull[fc_attnum];

			EEO_NEXT();
		}

		EEO_CASE(EEOP_ASSIGN_SCAN_VAR)
		{
			int			fc_resultnum = fc_op->d.assign_var.resultnum;
			int			fc_attnum = fc_op->d.assign_var.attnum;

			/*
			 * 我们在这里不需要 CheckVarSlotCompatibility；这个在编译时已经处理了。但请参阅 EEOP_INNER_VAR 注释。
			 */
			Assert(fc_attnum >= 0 && fc_attnum < fc_scanslot->tts_nvalid);
			Assert(fc_resultnum >= 0 && fc_resultnum < fc_resultslot->tts_tupleDescriptor->natts);
			fc_resultslot->tts_values[fc_resultnum] = fc_scanslot->tts_values[fc_attnum];
			fc_resultslot->tts_isnull[fc_resultnum] = fc_scanslot->tts_isnull[fc_attnum];

			EEO_NEXT();
		}

		EEO_CASE(EEOP_ASSIGN_TMP)
		{
			int			fc_resultnum = fc_op->d.assign_tmp.resultnum;

			Assert(fc_resultnum >= 0 && fc_resultnum < fc_resultslot->tts_tupleDescriptor->natts);
			fc_resultslot->tts_values[fc_resultnum] = fc_state->resvalue;
			fc_resultslot->tts_isnull[fc_resultnum] = fc_state->resnull;

			EEO_NEXT();
		}

		EEO_CASE(EEOP_ASSIGN_TMP_MAKE_RO)
		{
			int			fc_resultnum = fc_op->d.assign_tmp.resultnum;

			Assert(fc_resultnum >= 0 && fc_resultnum < fc_resultslot->tts_tupleDescriptor->natts);
			fc_resultslot->tts_isnull[fc_resultnum] = fc_state->resnull;
			if (!fc_resultslot->tts_isnull[fc_resultnum])
				fc_resultslot->tts_values[fc_resultnum] =
					MakeExpandedObjectReadOnlyInternal(fc_state->resvalue);
			else
				fc_resultslot->tts_values[fc_resultnum] = fc_state->resvalue;

			EEO_NEXT();
		}

		EEO_CASE(EEOP_CONST)
		{
			*fc_op->resnull = fc_op->d.constval.isnull;
			*fc_op->resvalue = fc_op->d.constval.value;

			EEO_NEXT();
		}

		/*
		 * 函数调用实现。参数之前直接评估到 fcinfo->args 中。
		 *
		 * 由于严格检查和函数使用在性能上是显著的，并且函数调用是一个非常热点路径（它们也反向操作符！），所以有这么多单独的操作码是值得的。
		 *
		 * 注意：在这里和其他地方使用临时变量 "d" 的原因是，有些编译器认为 "*op->resvalue = f();" 需要将 op->resvalue 评估到寄存器中，然后再调用 f()，以防 f() 能以某种方式修改 op->resvalue。额外的一行代码可以节省一次无用的寄存器溢出和加载。
		 */
		EEO_CASE(EEOP_FUNCEXPR)
		{
			FunctionCallInfo fcinfo = fc_op->d.func.fcinfo_data;
			Datum		fc_d;

			fcinfo->isnull = false;
			fc_d = fc_op->d.func.fn_addr(fcinfo);
			*fc_op->resvalue = fc_d;
			*fc_op->resnull = fcinfo->isnull;

			EEO_NEXT();
		}

		EEO_CASE(EEOP_FUNCEXPR_STRICT)
		{
			FunctionCallInfo fcinfo = fc_op->d.func.fcinfo_data;
			NullableDatum *fc_args = fcinfo->args;
			int			fc_nargs = fc_op->d.func.nargs;
			Datum		fc_d;

			/* 严格函数，因此检查 NULL 参数 */
			for (int fc_argno = 0; fc_argno < fc_nargs; fc_argno++)
			{
				if (fc_args[fc_argno].isnull)
				{
					*fc_op->resnull = true;
					goto strictfail;
				}
			}
			fcinfo->isnull = false;
			fc_d = fc_op->d.func.fn_addr(fcinfo);
			*fc_op->resvalue = fc_d;
			*fc_op->resnull = fcinfo->isnull;

	strictfail:
			EEO_NEXT();
		}

		EEO_CASE(EEOP_FUNCEXPR_FUSAGE)
		{
			/* 不够常见以进行内联 */
			ExecEvalFuncExprFusage(fc_state, fc_op, fc_econtext);

			EEO_NEXT();
		}

		EEO_CASE(EEOP_FUNCEXPR_STRICT_FUSAGE)
		{
			/* 不够常见以进行内联 */
			ExecEvalFuncExprStrictFusage(fc_state, fc_op, fc_econtext);

			EEO_NEXT();
		}

		/*
		 * 如果其任何子句为 FALSE，则 AND 的结果为 FALSE，无论其余子句的状态如何，因此我们可以停止评估并立即返回 FALSE。如果没有子句为 FALSE 并且一个或多个为 NULL，我们返回 NULL；否则返回 TRUE。当你将 NULL 理解为 "不知道" 时，这是有意义的：也许如果我们知道其中一个 "不知道" 的值，它将是 FALSE。只有当所有输入都已知为 TRUE 时，我们才能自信地说 AND 的结果为 TRUE。
		 */
		EEO_CASE(EEOP_BOOL_AND_STEP_FIRST)
		{
			*fc_op->d.boolexpr.anynull = false;

			/*
			 * EEOP_BOOL_AND_STEP_FIRST 重置 anynull，否则它与 EEOP_BOOL_AND_STEP 相同 - 所以继续到那。
			 */

			/* FALL THROUGH */
		}

		EEO_CASE(EEOP_BOOL_AND_STEP)
		{
			if (*fc_op->resnull)
			{
				*fc_op->d.boolexpr.anynull = true;
			}
			else if (!DatumGetBool(*fc_op->resvalue))
			{
				/* 结果已经设置为 FALSE，不需要更改它 */
				/* 提前退出 */
				EEO_JUMP(fc_op->d.boolexpr.jumpdone);
			}

			EEO_NEXT();
		}

		EEO_CASE(EEOP_BOOL_AND_STEP_LAST)
		{
			if (*fc_op->resnull)
			{
				/* 结果已经设置为 NULL，不需要更改它 */
			}
			else if (!DatumGetBool(*fc_op->resvalue))
			{
				/* 结果已经设置为 FALSE，不需要更改它 */

				/*
				 * 提前跳转到 jumpdone 没有意义 - 目标相同（因为这是 AND 表达式的最后一个参数），只不过更昂贵。
				 */
			}
			else if (*fc_op->d.boolexpr.anynull)
			{
				*fc_op->resvalue = (Datum) 0;
				*fc_op->resnull = true;
			}
			else
			{
				/* 结果已经设置为 TRUE，不需要更改它 */
			}

			EEO_NEXT();
		}

		/*
		 * 如果其任何子句为 TRUE，则 OR 的结果为 TRUE，无论其余子句的状态如何，因此我们可以停止评估并立即返回 TRUE。如果没有子句为 TRUE 并且一个或多个为 NULL，我们返回 NULL；否则返回 FALSE。当你将 NULL 理解为 "不知道" 时，这是有意义的：也许如果我们知道其中一个 "不知道" 的值，它将是 TRUE。只有当所有输入都已知为 FALSE 时，我们才能自信地说 OR 的结果为 FALSE。
		 */
		EEO_CASE(EEOP_BOOL_OR_STEP_FIRST)
		{
			*fc_op->d.boolexpr.anynull = false;

			/*
			 * EEOP_BOOL_OR_STEP_FIRST 重置 anynull，否则它与 EEOP_BOOL_OR_STEP 相同 - 所以继续到那。
			 */

			/* FALL THROUGH */
		}

		EEO_CASE(EEOP_BOOL_OR_STEP)
		{
			if (*fc_op->resnull)
			{
				*fc_op->d.boolexpr.anynull = true;
			}
			else if (DatumGetBool(*fc_op->resvalue))
			{
				/* 结果已经设置为 TRUE，不需要更改它 */
				/* 提前退出 */
				EEO_JUMP(fc_op->d.boolexpr.jumpdone);
			}

			EEO_NEXT();
		}

		EEO_CASE(EEOP_BOOL_OR_STEP_LAST)
		{
			if (*fc_op->resnull)
			{
				/* 结果已经设置为 NULL，不需要更改它 */
			}
			else if (DatumGetBool(*fc_op->resvalue))
			{
				/* 结果已经设置为 TRUE，不需要更改它 */

				/*
				 * 提前跳转到 jumpdone 没有意义 - 目标相同（因为这是 AND 表达式的最后一个参数），只不过更昂贵。
				 */
			}
			else if (*fc_op->d.boolexpr.anynull)
			{
				*fc_op->resvalue = (Datum) 0;
				*fc_op->resnull = true;
			}
			else
			{
				/* 结果已经设置为 FALSE，不需要更改它 */
			}

			EEO_NEXT();
		}

		EEO_CASE(EEOP_BOOL_NOT_STEP)
		{
			/*
			 * ‘not’ 的评估很简单……如果 expr 为 false，则返回 'true'，反之亦然。即使在名义上为空值上这样做也是安全的，因此我们忽略 resnull；这意味着 NULL 输入产生 NULL 输出，这正是我们想要的。
			 */
			*fc_op->resvalue = BoolGetDatum(!DatumGetBool(*fc_op->resvalue));

			EEO_NEXT();
		}

		EEO_CASE(EEOP_QUAL)
		{
			/* BOOL_AND_STEP 的简化版本，用于 ExecQual() */

			/* 如果参数（也是结果）为 false 或 null ... */
			if (*fc_op->resnull ||
				!DatumGetBool(*fc_op->resvalue))
			{
				/* ... 提前退出，返回 FALSE */
				*fc_op->resnull = false;
				*fc_op->resvalue = BoolGetDatum(false);
				EEO_JUMP(fc_op->d.qualexpr.jumpdone);
			}

			/*
			 * 否则，保持 TRUE 值不变，以防这是最后一个 qual。然后，TRUE 是正确的答案。
			 */

			EEO_NEXT();
		}

		EEO_CASE(EEOP_JUMP)
		{
			/* 无条件跳转到目标步骤 */
			EEO_JUMP(fc_op->d.jump.jumpdone);
		}

		EEO_CASE(EEOP_JUMP_IF_NULL)
		{
			/* 如果当前结果为 null，则转移控制 */
			if (*fc_op->resnull)
				EEO_JUMP(fc_op->d.jump.jumpdone);

			EEO_NEXT();
		}

		EEO_CASE(EEOP_JUMP_IF_NOT_NULL)
		{
			/* 如果当前结果为非空，则转移控制 */
			if (!*fc_op->resnull)
				EEO_JUMP(fc_op->d.jump.jumpdone);

			EEO_NEXT();
		}

		EEO_CASE(EEOP_JUMP_IF_NOT_TRUE)
		{
			/* 如果当前结果为 null 或 false，则转移控制 */
			if (*fc_op->resnull || !DatumGetBool(*fc_op->resvalue))
				EEO_JUMP(fc_op->d.jump.jumpdone);

			EEO_NEXT();
		}

		EEO_CASE(EEOP_NULLTEST_ISNULL)
		{
			*fc_op->resvalue = BoolGetDatum(*fc_op->resnull);
			*fc_op->resnull = false;

			EEO_NEXT();
		}

		EEO_CASE(EEOP_NULLTEST_ISNOTNULL)
		{
			*fc_op->resvalue = BoolGetDatum(!*fc_op->resnull);
			*fc_op->resnull = false;

			EEO_NEXT();
		}

		EEO_CASE(EEOP_NULLTEST_ROWISNULL)
		{
			/* 不在行内实现：太大 */
			ExecEvalRowNull(fc_state, fc_op, fc_econtext);

			EEO_NEXT();
		}

		EEO_CASE(EEOP_NULLTEST_ROWISNOTNULL)
		{
			/* 不在行内实现：太大 */
			ExecEvalRowNotNull(fc_state, fc_op, fc_econtext);

			EEO_NEXT();
		}

		/* 所有 booltesttypes 的 BooleanTest 实现 */

		EEO_CASE(EEOP_BOOLTEST_IS_TRUE)
		{
			if (*fc_op->resnull)
			{
				*fc_op->resvalue = BoolGetDatum(false);
				*fc_op->resnull = false;
			}
			/* 否则，输入值也是正确的输出 */

			EEO_NEXT();
		}

		EEO_CASE(EEOP_BOOLTEST_IS_NOT_TRUE)
		{
			if (*fc_op->resnull)
			{
				*fc_op->resvalue = BoolGetDatum(true);
				*fc_op->resnull = false;
			}
			else
				*fc_op->resvalue = BoolGetDatum(!DatumGetBool(*fc_op->resvalue));

			EEO_NEXT();
		}

		EEO_CASE(EEOP_BOOLTEST_IS_FALSE)
		{
			if (*fc_op->resnull)
			{
				*fc_op->resvalue = BoolGetDatum(false);
				*fc_op->resnull = false;
			}
			else
				*fc_op->resvalue = BoolGetDatum(!DatumGetBool(*fc_op->resvalue));

			EEO_NEXT();
		}

		EEO_CASE(EEOP_BOOLTEST_IS_NOT_FALSE)
		{
			if (*fc_op->resnull)
			{
				*fc_op->resvalue = BoolGetDatum(true);
				*fc_op->resnull = false;
			}
			/* 否则，输入值也是正确的输出 */

			EEO_NEXT();
		}

		EEO_CASE(EEOP_PARAM_EXEC)
		{
			/* 不在行内实现：太大 */
			ExecEvalParamExec(fc_state, fc_op, fc_econtext);

			EEO_NEXT();
		}

		EEO_CASE(EEOP_PARAM_EXTERN)
		{
			/* 不在行内实现：太大 */
			ExecEvalParamExtern(fc_state, fc_op, fc_econtext);
			EEO_NEXT();
		}

		EEO_CASE(EEOP_PARAM_CALLBACK)
		{
			/* 允许扩展模块提供 PARAM_EXTERN 值 */
			fc_op->d.cparam.paramfunc(fc_state, fc_op, fc_econtext);
			EEO_NEXT();
		}

		EEO_CASE(EEOP_CASE_TESTVAL)
		{
			/*
			 * 通常情况下，表达式树的上部设置了
			 * 要返回的值，但是系统的某些部分
			 * 当前错误地使用 {caseValue,domainValue}_{datum,isNull} 来设置
			 * 运行时数据。所以如果没有设置值，就使用
			 * ExprContext 的值。这并不是很美观，但也不算 *那么* 丑，
			 * 并且这不太可能是性能敏感到需要
			 * 担心多一个分支。
			 */
			if (fc_op->d.casetest.value)
			{
				*fc_op->resvalue = *fc_op->d.casetest.value;
				*fc_op->resnull = *fc_op->d.casetest.isnull;
			}
			else
			{
				*fc_op->resvalue = fc_econtext->caseValue_datum;
				*fc_op->resnull = fc_econtext->caseValue_isNull;
			}

			EEO_NEXT();
		}

		EEO_CASE(EEOP_DOMAIN_TESTVAL)
		{
			/*
			 * 参见 EEOP_CASE_TESTVAL 注释。
			 */
			if (fc_op->d.casetest.value)
			{
				*fc_op->resvalue = *fc_op->d.casetest.value;
				*fc_op->resnull = *fc_op->d.casetest.isnull;
			}
			else
			{
				*fc_op->resvalue = fc_econtext->domainValue_datum;
				*fc_op->resnull = fc_econtext->domainValue_isNull;
			}

			EEO_NEXT();
		}

		EEO_CASE(EEOP_MAKE_READONLY)
		{
			/*
			 * 强制一个可能被多次读取的 varlena 值为 R/O
			 */
			if (!*fc_op->d.make_readonly.isnull)
				*fc_op->resvalue =
					MakeExpandedObjectReadOnlyInternal(*fc_op->d.make_readonly.value);
			*fc_op->resnull = *fc_op->d.make_readonly.isnull;

			EEO_NEXT();
		}

		EEO_CASE(EEOP_IOCOERCE)
		{
			/*
			 * 评估一个 CoerceViaIO 节点。这个路径可能相当热，
			 * 所以尽可能内联更多工作。源值在我们的
			 * 结果变量中。
			 */
			char	   *fc_str;

			/* 调用输出函数（类似于 OutputFunctionCall） */
			if (*fc_op->resnull)
			{
				/* 输出函数不在空值上调用 */
				fc_str = NULL;
			}
			else
			{
				FunctionCallInfo fc_fcinfo_out;

				fc_fcinfo_out = fc_op->d.iocoerce.fcinfo_data_out;
				fc_fcinfo_out->args[0].value = *fc_op->resvalue;
				fc_fcinfo_out->args[0].isnull = false;

				fc_fcinfo_out->isnull = false;
				fc_str = DatumGetCString(FunctionCallInvoke(fc_fcinfo_out));

				/* OutputFunctionCall 假设结果不为空 */
				Assert(!fc_fcinfo_out->isnull);
			}

			/* 调用输入函数（类似于 InputFunctionCall） */
			if (!fc_op->d.iocoerce.finfo_in->fn_strict || fc_str != NULL)
			{
				FunctionCallInfo fc_fcinfo_in;
				Datum		fc_d;

				fc_fcinfo_in = fc_op->d.iocoerce.fcinfo_data_in;
				fc_fcinfo_in->args[0].value = PointerGetDatum(fc_str);
				fc_fcinfo_in->args[0].isnull = *fc_op->resnull;
				/* 第二和第三个参数已经设置好 */

				fc_fcinfo_in->isnull = false;
				fc_d = FunctionCallInvoke(fc_fcinfo_in);
				*fc_op->resvalue = fc_d;

				/* 只有在 str 为 NULL 时才应返回 null 结果 */
				if (fc_str == NULL)
				{
					Assert(*fc_op->resnull);
					Assert(fc_fcinfo_in->isnull);
				}
				else
				{
					Assert(!*fc_op->resnull);
					Assert(!fc_fcinfo_in->isnull);
				}
			}

			EEO_NEXT();
		}

		EEO_CASE(EEOP_DISTINCT)
		{
			/*
			 * IS DISTINCT FROM 必须评估参数（已经评估到
			 * fcinfo->args）以确定它们是否为 NULL；如果其中任一为
			 * NULL，则结果已确定。如果均不为 NULL，则
			 * 继续评估比较函数，即只是
			 * 类型的标准相等运算符。我们不需要关心该函数是否严格。
			 * 由于对空值的处理是不同的，我们不能直接重用 EEOP_FUNCEXPR。
			 */
			FunctionCallInfo fcinfo = fc_op->d.func.fcinfo_data;

			/* 检查函数参数是否为 NULL */
			if (fcinfo->args[0].isnull && fcinfo->args[1].isnull)
			{
				/* 两者都是 NULL？那么不是不同... */
				*fc_op->resvalue = BoolGetDatum(false);
				*fc_op->resnull = false;
			}
			else if (fcinfo->args[0].isnull || fcinfo->args[1].isnull)
			{
				/* 只有一个是 NULL？那么是不同... */
				*fc_op->resvalue = BoolGetDatum(true);
				*fc_op->resnull = false;
			}
			else
			{
				/* 两者都不为 NULL，因此应用相等函数 */
				Datum		fc_eqresult;

				fcinfo->isnull = false;
				fc_eqresult = fc_op->d.func.fn_addr(fcinfo);
				/* 必须反转 "=" 的结果；即使是 null 也可以安全执行 */
				*fc_op->resvalue = BoolGetDatum(!DatumGetBool(fc_eqresult));
				*fc_op->resnull = fcinfo->isnull;
			}

			EEO_NEXT();
		}

		/* 参见 EEOP_DISTINCT 的注释，这只是反转 */
		EEO_CASE(EEOP_NOT_DISTINCT)
		{
			FunctionCallInfo fcinfo = fc_op->d.func.fcinfo_data;

			if (fcinfo->args[0].isnull && fcinfo->args[1].isnull)
			{
				*fc_op->resvalue = BoolGetDatum(true);
				*fc_op->resnull = false;
			}
			else if (fcinfo->args[0].isnull || fcinfo->args[1].isnull)
			{
				*fc_op->resvalue = BoolGetDatum(false);
				*fc_op->resnull = false;
			}
			else
			{
				Datum		fc_eqresult;

				fcinfo->isnull = false;
				fc_eqresult = fc_op->d.func.fn_addr(fcinfo);
				*fc_op->resvalue = fc_eqresult;
				*fc_op->resnull = fcinfo->isnull;
			}

			EEO_NEXT();
		}

		EEO_CASE(EEOP_NULLIF)
		{
			/*
			 * 参数已经被评估到 fcinfo->args 中。
			 */
			FunctionCallInfo fcinfo = fc_op->d.func.fcinfo_data;
			Datum		fc_save_arg0 = fcinfo->args[0].value;

			/* 如果任一参数为 NULL，则它们不能相等 */
			if (!fcinfo->args[0].isnull && !fcinfo->args[1].isnull)
			{
				Datum		fc_result;

				/*
				 * 如果第一个参数是 varlena 类型，可能是一个
				 * 扩展数据项。我们需要确保传递给
				 * 比较函数的值是只读指针。但如果我们最后返回第一个参数，
				 * 那将是原始的读写指针，如果它是读写的话。
				 */
				if (fc_op->d.func.make_ro)
					fcinfo->args[0].value =
						MakeExpandedObjectReadOnlyInternal(fc_save_arg0);

				fcinfo->isnull = false;
				fc_result = fc_op->d.func.fn_addr(fcinfo);

				/* 如果参数相等，返回 null */
				if (!fcinfo->isnull && DatumGetBool(fc_result))
				{
					*fc_op->resvalue = (Datum) 0;
					*fc_op->resnull = true;

					EEO_NEXT();
				}
			}

			/* 参数不相等，因此返回第一个参数 */
			*fc_op->resvalue = fc_save_arg0;
			*fc_op->resnull = fcinfo->args[0].isnull;

			EEO_NEXT();
		}

		EEO_CASE(EEOP_SQLVALUEFUNCTION)
		{
			/*
			 * 在效率方面，似乎没有必要实现内联实现。
			 */
			ExecEvalSQLValueFunction(fc_state, fc_op);

			EEO_NEXT();
		}

		EEO_CASE(EEOP_CURRENTOFEXPR)
		{
			/* 错误调用使用空间，并且不应发生 */
			ExecEvalCurrentOfExpr(fc_state, fc_op);

			EEO_NEXT();
		}

		EEO_CASE(EEOP_NEXTVALUEEXPR)
		{
			/*
			 * 在效率方面，似乎没有必要实现内联实现。
			 */
			ExecEvalNextValueExpr(fc_state, fc_op);

			EEO_NEXT();
		}

		EEO_CASE(EEOP_ARRAYEXPR)
		{
			/* 对内联实现来说太复杂 */
			ExecEvalArrayExpr(fc_state, fc_op);

			EEO_NEXT();
		}

		EEO_CASE(EEOP_ARRAYCOERCE)
		{
			/* 对内联实现来说太复杂 */
			ExecEvalArrayCoerce(fc_state, fc_op, fc_econtext);

			EEO_NEXT();
		}

		EEO_CASE(EEOP_ROW)
		{
			/* 对内联实现来说太复杂 */
			ExecEvalRow(fc_state, fc_op);

			EEO_NEXT();
		}

		EEO_CASE(EEOP_ROWCOMPARE_STEP)
		{
			FunctionCallInfo fcinfo = fc_op->d.rowcompare_step.fcinfo_data;
			Datum		fc_d;

			/* 如果严格函数和 NULL 输入则强制返回 NULL 结果 */
			if (fc_op->d.rowcompare_step.finfo->fn_strict &&
				(fcinfo->args[0].isnull || fcinfo->args[1].isnull))
			{
				*fc_op->resnull = true;
				EEO_JUMP(fc_op->d.rowcompare_step.jumpnull);
			}

			/* 应用比较函数 */
			fcinfo->isnull = false;
			fc_d = fc_op->d.rowcompare_step.fn_addr(fcinfo);
			*fc_op->resvalue = fc_d;

			/* 如果函数结果为 NULL，则强制返回 NULL 结果 */
			if (fcinfo->isnull)
			{
				*fc_op->resnull = true;
				EEO_JUMP(fc_op->d.rowcompare_step.jumpnull);
			}
			*fc_op->resnull = false;

			/* 如果不相等，则无需比较剩余列 */
			if (DatumGetInt32(*fc_op->resvalue) != 0)
			{
				EEO_JUMP(fc_op->d.rowcompare_step.jumpdone);
			}

			EEO_NEXT();
		}

		EEO_CASE(EEOP_ROWCOMPARE_FINAL)
		{
			int32		fc_cmpresult = DatumGetInt32(*fc_op->resvalue);
			RowCompareType fc_rctype = fc_op->d.rowcompare_final.rctype;

			*fc_op->resnull = false;
			switch (fc_rctype)
			{
					/* EQ和NE情况在这里不被允许 */
				case ROWCOMPARE_LT:
					*fc_op->resvalue = BoolGetDatum(fc_cmpresult < 0);
					break;
				case ROWCOMPARE_LE:
					*fc_op->resvalue = BoolGetDatum(fc_cmpresult <= 0);
					break;
				case ROWCOMPARE_GE:
					*fc_op->resvalue = BoolGetDatum(fc_cmpresult >= 0);
					break;
				case ROWCOMPARE_GT:
					*fc_op->resvalue = BoolGetDatum(fc_cmpresult > 0);
					break;
				default:
					Assert(false);
					break;
			}

			EEO_NEXT();
		}

		EEO_CASE(EEOP_MINMAX)
		{
			/* 对内联实现来说太复杂 */
			ExecEvalMinMax(fc_state, fc_op);

			EEO_NEXT();
		}

		EEO_CASE(EEOP_FIELDSELECT)
		{
			/* 对内联实现来说太复杂 */
			ExecEvalFieldSelect(fc_state, fc_op, fc_econtext);

			EEO_NEXT();
		}

		EEO_CASE(EEOP_FIELDSTORE_DEFORM)
		{
			/* 对内联实现来说太复杂 */
			ExecEvalFieldStoreDeForm(fc_state, fc_op, fc_econtext);

			EEO_NEXT();
		}

		EEO_CASE(EEOP_FIELDSTORE_FORM)
		{
			/* 对内联实现来说太复杂 */
			ExecEvalFieldStoreForm(fc_state, fc_op, fc_econtext);

			EEO_NEXT();
		}

		EEO_CASE(EEOP_SBSREF_SUBSCRIPTS)
		{
			/* 预检查 SubscriptingRef 下标 */
			if (fc_op->d.sbsref_subscript.subscriptfunc(fc_state, fc_op, fc_econtext))
			{
				EEO_NEXT();
			}
			else
			{
				/* 下标为 null，短路 SubscriptingRef 为 NULL */
				EEO_JUMP(fc_op->d.sbsref_subscript.jumpdone);
			}
		}

		EEO_CASE(EEOP_SBSREF_OLD)
			EEO_CASE(EEOP_SBSREF_ASSIGN)
			EEO_CASE(EEOP_SBSREF_FETCH)
		{
			/* 执行子脚本引用的获取或赋值 */
			fc_op->d.sbsref.subscriptfunc(fc_state, fc_op, fc_econtext);

			EEO_NEXT();
		}

		EEO_CASE(EEOP_CONVERT_ROWTYPE)
		{
			/* 对内联实现来说太复杂 */
			ExecEvalConvertRowtype(fc_state, fc_op, fc_econtext);

			EEO_NEXT();
		}

		EEO_CASE(EEOP_SCALARARRAYOP)
		{
			/* 对内联实现来说太复杂 */
			ExecEvalScalarArrayOp(fc_state, fc_op);

			EEO_NEXT();
		}

		EEO_CASE(EEOP_HASHED_SCALARARRAYOP)
		{
			/* 对内联实现来说太复杂 */
			ExecEvalHashedScalarArrayOp(fc_state, fc_op, fc_econtext);

			EEO_NEXT();
		}

		EEO_CASE(EEOP_DOMAIN_NOTNULL)
		{
			/* 对内联实现来说太复杂 */
			ExecEvalConstraintNotNull(fc_state, fc_op);

			EEO_NEXT();
		}

		EEO_CASE(EEOP_DOMAIN_CHECK)
		{
			/* 对内联实现来说太复杂 */
			ExecEvalConstraintCheck(fc_state, fc_op);

			EEO_NEXT();
		}

		EEO_CASE(EEOP_XMLEXPR)
		{
			/* 对内联实现来说太复杂 */
			ExecEvalXmlExpr(fc_state, fc_op);

			EEO_NEXT();
		}

		EEO_CASE(EEOP_AGGREF)
		{
			/*
			 * 返回一个 Datum，其值是给定表达式上下文中的预计算聚合值。
			 */
			int			fc_aggno = fc_op->d.aggref.aggno;

			Assert(fc_econtext->ecxt_aggvalues != NULL);

			*fc_op->resvalue = fc_econtext->ecxt_aggvalues[fc_aggno];
			*fc_op->resnull = fc_econtext->ecxt_aggnulls[fc_aggno];

			EEO_NEXT();
		}

		EEO_CASE(EEOP_GROUPING_FUNC)
		{
			/* 过于复杂/不常见，无法进行内联实现 */
			ExecEvalGroupingFunc(fc_state, fc_op);

			EEO_NEXT();
		}

		EEO_CASE(EEOP_WINDOW_FUNC)
		{
			/*
			 * 像 Aggref 一样，从 econtext 返回一个预计算值。
			 */
			WindowFuncExprState *fc_wfunc = fc_op->d.window_func.wfstate;

			Assert(fc_econtext->ecxt_aggvalues != NULL);

			*fc_op->resvalue = fc_econtext->ecxt_aggvalues[fc_wfunc->wfuncno];
			*fc_op->resnull = fc_econtext->ecxt_aggnulls[fc_wfunc->wfuncno];

			EEO_NEXT();
		}

		EEO_CASE(EEOP_SUBPLAN)
		{
			/* 对内联实现来说太复杂 */
			ExecEvalSubPlan(fc_state, fc_op, fc_econtext);

			EEO_NEXT();
		}

		/* 评估严格的聚合反序列化函数 */
		EEO_CASE(EEOP_AGG_STRICT_DESERIALIZE)
		{
			/* 不要用 NULL 输入调用严格的反序列化函数 */
			if (fc_op->d.agg_deserialize.fcinfo_data->args[0].isnull)
				EEO_JUMP(fc_op->d.agg_deserialize.jumpnull);

			/* 穿透 */
		}

		/* 评估聚合反序列化函数（非严格部分） */
		EEO_CASE(EEOP_AGG_DESERIALIZE)
		{
			FunctionCallInfo fcinfo = fc_op->d.agg_deserialize.fcinfo_data;
			AggState   *fc_aggstate = castNode(AggState, fc_state->parent);
			MemoryContext fc_oldContext;

			/*
			 * 我们在每个输入元组的内存上下文中运行反序列化函数。
			 */
			fc_oldContext = MemoryContextSwitchTo(fc_aggstate->tmpcontext->ecxt_per_tuple_memory);
			fcinfo->isnull = false;
			*fc_op->resvalue = FunctionCallInvoke(fcinfo);
			*fc_op->resnull = fcinfo->isnull;
			MemoryContextSwitchTo(fc_oldContext);

			EEO_NEXT();
		}

		/*
		 * 检查严格聚合过渡/组合函数的输入是否为 NULL。
		 */

		EEO_CASE(EEOP_AGG_STRICT_INPUT_CHECK_ARGS)
		{
			NullableDatum *fc_args = fc_op->d.agg_strict_input_check.args;
			int			fc_nargs = fc_op->d.agg_strict_input_check.nargs;

			for (int fc_argno = 0; fc_argno < fc_nargs; fc_argno++)
			{
				if (fc_args[fc_argno].isnull)
					EEO_JUMP(fc_op->d.agg_strict_input_check.jumpnull);
			}
			EEO_NEXT();
		}

		EEO_CASE(EEOP_AGG_STRICT_INPUT_CHECK_NULLS)
		{
			bool	   *fc_nulls = fc_op->d.agg_strict_input_check.nulls;
			int			fc_nargs = fc_op->d.agg_strict_input_check.nargs;

			for (int fc_argno = 0; fc_argno < fc_nargs; fc_argno++)
			{
				if (fc_nulls[fc_argno])
					EEO_JUMP(fc_op->d.agg_strict_input_check.jumpnull);
			}
			EEO_NEXT();
		}

		/*
		 * 检查每个组状态的 NULL 指针。
		 */

		EEO_CASE(EEOP_AGG_PLAIN_PERGROUP_NULLCHECK)
		{
			AggState   *fc_aggstate = castNode(AggState, fc_state->parent);
			AggStatePerGroup fc_pergroup_allaggs =
			fc_aggstate->all_pergroups[fc_op->d.agg_plain_pergroup_nullcheck.setoff];

			if (fc_pergroup_allaggs == NULL)
				EEO_JUMP(fc_op->d.agg_plain_pergroup_nullcheck.jumpnull);

			EEO_NEXT();
		}

		/*
		 * 不同类型的聚合过渡函数被实现为不同类型的步骤，以避免产生不必要的开销。
		 * 每个有效组合都有一个步骤类型，包括基于值/基于引用的过渡类型，是否需要从输入初始化组中第一行的过渡值，以及是否严格过渡函数。
		 *
		 * 通过将固定长度类型的引用分离来进一步优化是可行的，但目前看起来没有必要。
		 */

		EEO_CASE(EEOP_AGG_PLAIN_TRANS_INIT_STRICT_BYVAL)
		{
			AggState   *fc_aggstate = castNode(AggState, fc_state->parent);
			AggStatePerTrans fc_pertrans = fc_op->d.agg_trans.pertrans;
			AggStatePerGroup fc_pergroup =
			&fc_aggstate->all_pergroups[fc_op->d.agg_trans.setoff][fc_op->d.agg_trans.transno];

			Assert(fc_pertrans->transtypeByVal);

			if (fc_pergroup->noTransValue)
			{
				/* 如果 transValue 尚未初始化，请立即进行初始化。 */
				ExecAggInitGroup(fc_aggstate, fc_pertrans, fc_pergroup,
								 fc_op->d.agg_trans.aggcontext);
				/* 从输入中复制的转移值，本轮完成 */
			}
			else if (likely(!fc_pergroup->transValueIsNull))
			{
				/* 调用过渡函数，除非因为严格性被阻止 */
				fc_ExecAggPlainTransByVal(fc_aggstate, fc_pertrans, fc_pergroup,
									   fc_op->d.agg_trans.aggcontext,
									   fc_op->d.agg_trans.setno);
			}

			EEO_NEXT();
		}

		/* 请参见上面的注释 EEOP_AGG_PLAIN_TRANS_INIT_STRICT_BYVAL */
		EEO_CASE(EEOP_AGG_PLAIN_TRANS_STRICT_BYVAL)
		{
			AggState   *fc_aggstate = castNode(AggState, fc_state->parent);
			AggStatePerTrans fc_pertrans = fc_op->d.agg_trans.pertrans;
			AggStatePerGroup fc_pergroup =
			&fc_aggstate->all_pergroups[fc_op->d.agg_trans.setoff][fc_op->d.agg_trans.transno];

			Assert(fc_pertrans->transtypeByVal);

			if (likely(!fc_pergroup->transValueIsNull))
				fc_ExecAggPlainTransByVal(fc_aggstate, fc_pertrans, fc_pergroup,
									   fc_op->d.agg_trans.aggcontext,
									   fc_op->d.agg_trans.setno);

			EEO_NEXT();
		}

		/* 请参见上面的注释 EEOP_AGG_PLAIN_TRANS_INIT_STRICT_BYVAL */
		EEO_CASE(EEOP_AGG_PLAIN_TRANS_BYVAL)
		{
			AggState   *fc_aggstate = castNode(AggState, fc_state->parent);
			AggStatePerTrans fc_pertrans = fc_op->d.agg_trans.pertrans;
			AggStatePerGroup fc_pergroup =
			&fc_aggstate->all_pergroups[fc_op->d.agg_trans.setoff][fc_op->d.agg_trans.transno];

			Assert(fc_pertrans->transtypeByVal);

			fc_ExecAggPlainTransByVal(fc_aggstate, fc_pertrans, fc_pergroup,
								   fc_op->d.agg_trans.aggcontext,
								   fc_op->d.agg_trans.setno);

			EEO_NEXT();
		}

		/* 请参见上面的注释 EEOP_AGG_PLAIN_TRANS_INIT_STRICT_BYVAL */
		EEO_CASE(EEOP_AGG_PLAIN_TRANS_INIT_STRICT_BYREF)
		{
			AggState   *fc_aggstate = castNode(AggState, fc_state->parent);
			AggStatePerTrans fc_pertrans = fc_op->d.agg_trans.pertrans;
			AggStatePerGroup fc_pergroup =
			&fc_aggstate->all_pergroups[fc_op->d.agg_trans.setoff][fc_op->d.agg_trans.transno];

			Assert(!fc_pertrans->transtypeByVal);

			if (fc_pergroup->noTransValue)
				ExecAggInitGroup(fc_aggstate, fc_pertrans, fc_pergroup,
								 fc_op->d.agg_trans.aggcontext);
			else if (likely(!fc_pergroup->transValueIsNull))
				fc_ExecAggPlainTransByRef(fc_aggstate, fc_pertrans, fc_pergroup,
									   fc_op->d.agg_trans.aggcontext,
									   fc_op->d.agg_trans.setno);

			EEO_NEXT();
		}

		/* 请参见上面的注释 EEOP_AGG_PLAIN_TRANS_INIT_STRICT_BYVAL */
		EEO_CASE(EEOP_AGG_PLAIN_TRANS_STRICT_BYREF)
		{
			AggState   *fc_aggstate = castNode(AggState, fc_state->parent);
			AggStatePerTrans fc_pertrans = fc_op->d.agg_trans.pertrans;
			AggStatePerGroup fc_pergroup =
			&fc_aggstate->all_pergroups[fc_op->d.agg_trans.setoff][fc_op->d.agg_trans.transno];

			Assert(!fc_pertrans->transtypeByVal);

			if (likely(!fc_pergroup->transValueIsNull))
				fc_ExecAggPlainTransByRef(fc_aggstate, fc_pertrans, fc_pergroup,
									   fc_op->d.agg_trans.aggcontext,
									   fc_op->d.agg_trans.setno);
			EEO_NEXT();
		}

		/* 请参见上面的注释 EEOP_AGG_PLAIN_TRANS_INIT_STRICT_BYVAL */
		EEO_CASE(EEOP_AGG_PLAIN_TRANS_BYREF)
		{
			AggState   *fc_aggstate = castNode(AggState, fc_state->parent);
			AggStatePerTrans fc_pertrans = fc_op->d.agg_trans.pertrans;
			AggStatePerGroup fc_pergroup =
			&fc_aggstate->all_pergroups[fc_op->d.agg_trans.setoff][fc_op->d.agg_trans.transno];

			Assert(!fc_pertrans->transtypeByVal);

			fc_ExecAggPlainTransByRef(fc_aggstate, fc_pertrans, fc_pergroup,
								   fc_op->d.agg_trans.aggcontext,
								   fc_op->d.agg_trans.setno);

			EEO_NEXT();
		}

		/* 处理单列有序聚合 datum */
		EEO_CASE(EEOP_AGG_ORDERED_TRANS_DATUM)
		{
			/* 对内联实现来说太复杂 */
			ExecEvalAggOrderedTransDatum(fc_state, fc_op, fc_econtext);

			EEO_NEXT();
		}

		/* 处理多列有序聚合元组 */
		EEO_CASE(EEOP_AGG_ORDERED_TRANS_TUPLE)
		{
			/* 对内联实现来说太复杂 */
			ExecEvalAggOrderedTransTuple(fc_state, fc_op, fc_econtext);

			EEO_NEXT();
		}

		EEO_CASE(EEOP_LAST)
		{
			/* unreachable */
			Assert(false);
			goto out;
		}
	}

out:
	*fc_isnull = fc_state->resnull;
	return fc_state->resvalue;
}

/*
 * 表达式评估回调，在执行表达式之前执行额外检查。声明为 extern，以便其他执行方法也可以使用它。
 */
Datum ExecInterpExprStillValid(ExprState *fc_state, ExprContext *fc_econtext, bool *fc_isNull)
{
	/*
	 * 第一次通过时，检查属性是否与 Var 匹配。由于架构变更，可能不再可用。
	 */
	CheckExprStillValid(fc_state, fc_econtext);

	/* 在进一步执行中跳过检查 */
	fc_state->evalfunc = (ExprStateEvalFunc) fc_state->evalfunc_private;

	/* 并实际执行 */
	return fc_state->evalfunc(fc_state, fc_econtext, fc_isNull);
}

/*
 * 检查在计划创建后，由于潜在的架构更改，表达式是否仍然有效。
 */
void CheckExprStillValid(ExprState *fc_state, ExprContext *fc_econtext)
{
	TupleTableSlot *fc_innerslot;
	TupleTableSlot *fc_outerslot;
	TupleTableSlot *fc_scanslot;

	fc_innerslot = fc_econtext->ecxt_innertuple;
	fc_outerslot = fc_econtext->ecxt_outertuple;
	fc_scanslot = fc_econtext->ecxt_scantuple;

	for (int fc_i = 0; fc_i < fc_state->steps_len; fc_i++)
	{
		ExprEvalStep *fc_op = &fc_state->steps[fc_i];

		switch (ExecEvalStepOp(fc_state, fc_op))
		{
			case EEOP_INNER_VAR:
				{
					int			fc_attnum = fc_op->d.var.attnum;

					fc_CheckVarSlotCompatibility(fc_innerslot, fc_attnum + 1, fc_op->d.var.vartype);
					break;
				}

			case EEOP_OUTER_VAR:
				{
					int			fc_attnum = fc_op->d.var.attnum;

					fc_CheckVarSlotCompatibility(fc_outerslot, fc_attnum + 1, fc_op->d.var.vartype);
					break;
				}

			case EEOP_SCAN_VAR:
				{
					int			fc_attnum = fc_op->d.var.attnum;

					fc_CheckVarSlotCompatibility(fc_scanslot, fc_attnum + 1, fc_op->d.var.vartype);
					break;
				}
			default:
				break;
		}
	}
}

/*
 * 检查插槽中的用户属性是否可以通过 Var 表达式引用。除非自表达式树创建以来发生架构更改，否则应成功。
 */
static void fc_CheckVarSlotCompatibility(TupleTableSlot *fc_slot, int fc_attnum, Oid fc_vartype)
{
	/*
	 * 我们在这里需要检查的是，自计划树创建以来，属性是否被删除或更改了类型。
	 * 理想情况下，计划将失效并不会被重用，但以防万一，我们保持这些防御措施。幸运的是，第一次通过时只需检查一次即可。
	 *
	 * 注意：理想情况下，我们还会检查 typmod 和 typid，但目前看起来不切实际：在许多情况下，tupdesc 将由 ExecTypeFromTL() 生成，而这无法保证在所有情况下生成准确的 typmod，因为某些表达式节点类型不携带 typmod。
	 * 幸运的是，正因如此，不应该有任何地方对一个值的 typmod 具有关键依赖关系。
	 *
	 * 系统属性不需要检查，因为它们的类型不会改变。
	 */
	if (fc_attnum > 0)
	{
		TupleDesc	fc_slot_tupdesc = fc_slot->tts_tupleDescriptor;
		Form_pg_attribute fc_attr;

		if (fc_attnum > fc_slot_tupdesc->natts)	/* 永远不应该发生 */
			elog(ERROR, "attribute number %d exceeds number of columns %d",
				 fc_attnum, fc_slot_tupdesc->natts);

		fc_attr = TupleDescAttr(fc_slot_tupdesc, fc_attnum - 1);

		if (fc_attr->attisdropped)
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_COLUMN),
					 errmsg("attribute %d of type %s has been dropped",
							fc_attnum, format_type_be(fc_slot_tupdesc->tdtypeid))));

		if (fc_vartype != fc_attr->atttypid)
			ereport(ERROR,
					(errcode(ERRCODE_DATATYPE_MISMATCH),
					 errmsg("attribute %d of type %s has wrong type",
							fc_attnum, format_type_be(fc_slot_tupdesc->tdtypeid)),
					 errdetail("Table has type %s, but query expects %s.",
							   format_type_be(fc_attr->atttypid),
							   format_type_be(fc_vartype))));
	}
}

/*
 * 验证插槽是否与 EEOP_*_FETCHSOME 操作兼容。
 */
static void fc_CheckOpSlotCompatibility(ExprEvalStep *fc_op, TupleTableSlot *fc_slot)
{
#ifdef USE_ASSERT_CHECKING
	/* 没有什么可以检查的 */
	if (!fc_op->d.fetch.fixed)
		return;

	/*
	 * 可能在某个时候需要修复，但目前允许缓冲区和堆元组可以交替使用更简单。
	 */
	if (fc_slot->tts_ops == &TTSOpsBufferHeapTuple &&
		fc_op->d.fetch.kind == &TTSOpsHeapTuple)
		return;
	if (fc_slot->tts_ops == &TTSOpsHeapTuple &&
		fc_op->d.fetch.kind == &TTSOpsBufferHeapTuple)
		return;

	/*
	 * 目前，如果使用虚拟插槽而非特定类型的插槽，我们认为是可以的，因为虚拟插槽从不需要变形。
	 */
	if (fc_slot->tts_ops == &TTSOpsVirtual)
		return;

	Assert(fc_op->d.fetch.kind == fc_slot->tts_ops);
#endif
}

/*
 * get_cached_rowtype: 查找 rowtype tupdesc 的实用函数
 *
 * type_id, typmod: rowtype 的标识
 * rowcache: 缓存标识信息的空间
 *		(rowcache->cacheptr 必须初始化为 NULL)
 * changed: 如果不为 NULL，则在任何更新时 *changed 设置为 true
 *
 * 返回的 TupleDesc 不保证被固定；调用者必须将其固定以在可能导致缓存失效的任何操作中使用，包括例如输入元组的取消烘焙。
 * （TupleDesc 始终是引用计数的，因此只需使用 IncrTupleDescRefCount。）
 *
 * 注意：由于复合类型可以更改内容，我们必须准备在任何节点执行期间重新执行此操作；不能仅在表达式初始化期间调用一次。
 */
static TupleDesc get_cached_rowtype(Oid fc_type_id, int32 fc_typmod,
				   ExprEvalRowtypeCache *fc_rowcache,
				   bool *fc_changed)
{
	if (fc_type_id != RECORDOID)
	{
		
		TypeCacheEntry *fc_typentry = (TypeCacheEntry *) fc_rowcache->cacheptr;

		if (unlikely(fc_typentry == NULL ||
					 fc_rowcache->tupdesc_id == 0 ||
					 fc_typentry->tupDesc_identifier != fc_rowcache->tupdesc_id))
		{
			fc_typentry = lookup_type_cache(fc_type_id, TYPECACHE_TUPDESC);
			if (fc_typentry->tupDesc == NULL)
				ereport(ERROR,
						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
						 errmsg("type %s is not composite",
								format_type_be(fc_type_id))));
			fc_rowcache->cacheptr = (void *) fc_typentry;
			fc_rowcache->tupdesc_id = fc_typentry->tupDesc_identifier;
			if (fc_changed)
				*fc_changed = true;
		}
		return fc_typentry->tupDesc;
	}
	else
	{
		/*
		 * RECORD类型一旦注册，对于后端的生命周期来说不会改变。
		 * 所以我们不需要一个typcache条目，这是好事，因为实际上没有一个。
		 * 但是调用者可能在询问一个不同类型。
		 */
		TupleDesc	fc_tupDesc = (TupleDesc) fc_rowcache->cacheptr;

		if (unlikely(fc_tupDesc == NULL ||
					 fc_rowcache->tupdesc_id != 0 ||
					 fc_type_id != fc_tupDesc->tdtypeid ||
					 fc_typmod != fc_tupDesc->tdtypmod))
		{
			fc_tupDesc = lookup_rowtype_tupdesc(fc_type_id, fc_typmod);
			/* 通过lookup_rowtype_tupdesc获得的drop pin */
			ReleaseTupleDesc(fc_tupDesc);
			fc_rowcache->cacheptr = (void *) fc_tupDesc;
			fc_rowcache->tupdesc_id = 0;	/* 对于非RECORD而言不是一个有效值 */
			if (fc_changed)
				*fc_changed = true;
		}
		return fc_tupDesc;
	}
}


/*
 * 快速路径函数，用于非常简单的表达式
 */

/* ExecJust(Inner|Outer|Scan)Var的实现 */
static pg_attribute_always_inline Datum fc_ExecJustVarImpl(ExprState *fc_state, TupleTableSlot *fc_slot, bool *fc_isnull)
{
	ExprEvalStep *fc_op = &fc_state->steps[1];
	int			fc_attnum = fc_op->d.var.attnum + 1;

	fc_CheckOpSlotCompatibility(&fc_state->steps[0], fc_slot);

	/*
	 * 由于我们使用slot_getattr()，我们不需要显式地实现FETCHSOME步骤，
	 * 我们也不需要断言attnum在范围内——slot_getattr()将处理任何问题。
	 */
	return slot_getattr(fc_slot, fc_attnum, fc_isnull);
}

/* 对inner Var的简单引用 */
static Datum fc_ExecJustInnerVar(ExprState *fc_state, ExprContext *fc_econtext, bool *fc_isnull)
{
	return fc_ExecJustVarImpl(fc_state, fc_econtext->ecxt_innertuple, fc_isnull);
}

/* 对outer Var的简单引用 */
static Datum fc_ExecJustOuterVar(ExprState *fc_state, ExprContext *fc_econtext, bool *fc_isnull)
{
	return fc_ExecJustVarImpl(fc_state, fc_econtext->ecxt_outertuple, fc_isnull);
}

/* 对scan Var的简单引用 */
static Datum fc_ExecJustScanVar(ExprState *fc_state, ExprContext *fc_econtext, bool *fc_isnull)
{
	return fc_ExecJustVarImpl(fc_state, fc_econtext->ecxt_scantuple, fc_isnull);
}

/* ExecJustAssign(Inner|Outer|Scan)Var的实现 */
static pg_attribute_always_inline Datum fc_ExecJustAssignVarImpl(ExprState *fc_state, TupleTableSlot *fc_inslot, bool *fc_isnull)
{
	ExprEvalStep *fc_op = &fc_state->steps[1];
	int			fc_attnum = fc_op->d.assign_var.attnum + 1;
	int			fc_resultnum = fc_op->d.assign_var.resultnum;
	TupleTableSlot *fc_outslot = fc_state->resultslot;

	fc_CheckOpSlotCompatibility(&fc_state->steps[0], fc_inslot);

	/*
	 * 我们在这里不需要CheckVarSlotCompatibility；在编译时已经处理过了。
	 *
	 * 由于我们使用slot_getattr()，我们不需要显式地实现FETCHSOME步骤，
	 * 我们也不需要断言attnum在范围内——slot_getattr()将处理任何问题。
	 * 尽管如此，检查resultnum是否在范围内。
	 */
	Assert(fc_resultnum >= 0 && fc_resultnum < fc_outslot->tts_tupleDescriptor->natts);
	fc_outslot->tts_values[fc_resultnum] =
		slot_getattr(fc_inslot, fc_attnum, &fc_outslot->tts_isnull[fc_resultnum]);
	return 0;
}

/* 评估inner Var并分配给结果元组的适当列 */
static Datum fc_ExecJustAssignInnerVar(ExprState *fc_state, ExprContext *fc_econtext, bool *fc_isnull)
{
	return fc_ExecJustAssignVarImpl(fc_state, fc_econtext->ecxt_innertuple, fc_isnull);
}

/* 评估outer Var并分配给结果元组的适当列 */
static Datum fc_ExecJustAssignOuterVar(ExprState *fc_state, ExprContext *fc_econtext, bool *fc_isnull)
{
	return fc_ExecJustAssignVarImpl(fc_state, fc_econtext->ecxt_outertuple, fc_isnull);
}

/* 评估scan Var并分配给结果元组的适当列 */
static Datum fc_ExecJustAssignScanVar(ExprState *fc_state, ExprContext *fc_econtext, bool *fc_isnull)
{
	return fc_ExecJustAssignVarImpl(fc_state, fc_econtext->ecxt_scantuple, fc_isnull);
}

/* 评估CASE_TESTVAL并对其应用严格函数 */
static Datum fc_ExecJustApplyFuncToCase(ExprState *fc_state, ExprContext *fc_econtext, bool *fc_isnull)
{
	ExprEvalStep *fc_op = &fc_state->steps[0];
	FunctionCallInfo fcinfo;
	NullableDatum *fc_args;
	int			fc_nargs;
	Datum		fc_d;

	/*
	 * XXX通过重新设计CaseTestExpr机制，也许我们可以
	 * 摆脱这种数据洗牌？
	 */
	*fc_op->resvalue = *fc_op->d.casetest.value;
	*fc_op->resnull = *fc_op->d.casetest.isnull;

	fc_op++;

	fc_nargs = fc_op->d.func.nargs;
	fcinfo = fc_op->d.func.fcinfo_data;
	fc_args = fcinfo->args;

	/* 严格函数，因此检查 NULL 参数 */
	for (int fc_argno = 0; fc_argno < fc_nargs; fc_argno++)
	{
		if (fc_args[fc_argno].isnull)
		{
			*fc_isnull = true;
			return (Datum) 0;
		}
	}
	fcinfo->isnull = false;
	fc_d = fc_op->d.func.fn_addr(fcinfo);
	*fc_isnull = fcinfo->isnull;
	return fc_d;
}

/* 简单的Const表达式 */
static Datum fc_ExecJustConst(ExprState *fc_state, ExprContext *fc_econtext, bool *fc_isnull)
{
	ExprEvalStep *fc_op = &fc_state->steps[0];

	*fc_isnull = fc_op->d.constval.isnull;
	return fc_op->d.constval.value;
}

/* ExecJust(Inner|Outer|Scan)VarVirt的实现 */
static pg_attribute_always_inline Datum fc_ExecJustVarVirtImpl(ExprState *fc_state, TupleTableSlot *fc_slot, bool *fc_isnull)
{
	ExprEvalStep *fc_op = &fc_state->steps[0];
	int			fc_attnum = fc_op->d.var.attnum;

	/*
	 * 由于保证使用虚拟槽，因此从来没有必要
	 * 执行元组变形（也不可能）。因此
	 * execExpr.c没有发出EEOP_*_FETCHSOME步骤。尽可能地验证
	 * 这种判断是准确的。
	 */
	Assert(TTS_IS_VIRTUAL(fc_slot));
	Assert(TTS_FIXED(fc_slot));
	Assert(fc_attnum >= 0 && fc_attnum < fc_slot->tts_nvalid);

	*fc_isnull = fc_slot->tts_isnull[fc_attnum];

	return fc_slot->tts_values[fc_attnum];
}

/* 类似于ExecJustInnerVar，针对虚拟槽进行了优化 */
static Datum fc_ExecJustInnerVarVirt(ExprState *fc_state, ExprContext *fc_econtext, bool *fc_isnull)
{
	return fc_ExecJustVarVirtImpl(fc_state, fc_econtext->ecxt_innertuple, fc_isnull);
}

/* 类似于ExecJustOuterVar，针对虚拟槽进行了优化 */
static Datum fc_ExecJustOuterVarVirt(ExprState *fc_state, ExprContext *fc_econtext, bool *fc_isnull)
{
	return fc_ExecJustVarVirtImpl(fc_state, fc_econtext->ecxt_outertuple, fc_isnull);
}

/* 类似于ExecJustScanVar，针对虚拟槽进行了优化 */
static Datum fc_ExecJustScanVarVirt(ExprState *fc_state, ExprContext *fc_econtext, bool *fc_isnull)
{
	return fc_ExecJustVarVirtImpl(fc_state, fc_econtext->ecxt_scantuple, fc_isnull);
}

/* ExecJustAssign(Inner|Outer|Scan)VarVirt的实现 */
static pg_attribute_always_inline Datum fc_ExecJustAssignVarVirtImpl(ExprState *fc_state, TupleTableSlot *fc_inslot, bool *fc_isnull)
{
	ExprEvalStep *fc_op = &fc_state->steps[0];
	int			fc_attnum = fc_op->d.assign_var.attnum;
	int			fc_resultnum = fc_op->d.assign_var.resultnum;
	TupleTableSlot *fc_outslot = fc_state->resultslot;

	/* 有关评论，请参见ExecJustVarVirtImpl */

	Assert(TTS_IS_VIRTUAL(fc_inslot));
	Assert(TTS_FIXED(fc_inslot));
	Assert(fc_attnum >= 0 && fc_attnum < fc_inslot->tts_nvalid);
	Assert(fc_resultnum >= 0 && fc_resultnum < fc_outslot->tts_tupleDescriptor->natts);

	fc_outslot->tts_values[fc_resultnum] = fc_inslot->tts_values[fc_attnum];
	fc_outslot->tts_isnull[fc_resultnum] = fc_inslot->tts_isnull[fc_attnum];

	return 0;
}

/* 类似于ExecJustAssignInnerVar，针对虚拟槽进行了优化 */
static Datum fc_ExecJustAssignInnerVarVirt(ExprState *fc_state, ExprContext *fc_econtext, bool *fc_isnull)
{
	return fc_ExecJustAssignVarVirtImpl(fc_state, fc_econtext->ecxt_innertuple, fc_isnull);
}

/* 类似于ExecJustAssignOuterVar，针对虚拟槽进行了优化 */
static Datum fc_ExecJustAssignOuterVarVirt(ExprState *fc_state, ExprContext *fc_econtext, bool *fc_isnull)
{
	return fc_ExecJustAssignVarVirtImpl(fc_state, fc_econtext->ecxt_outertuple, fc_isnull);
}

/* 类似于ExecJustAssignScanVar，针对虚拟槽进行了优化 */
static Datum fc_ExecJustAssignScanVarVirt(ExprState *fc_state, ExprContext *fc_econtext, bool *fc_isnull)
{
	return fc_ExecJustAssignVarVirtImpl(fc_state, fc_econtext->ecxt_scantuple, fc_isnull);
}

#if defined(EEO_USE_COMPUTED_GOTO)
/*
 * 在构建address->opcode查找表
 * ExecEvalStepOp()时使用的比较器。
 */
static int fc_dispatch_compare_ptr(const void *fc_a, const void *fc_b)
{
	const ExprEvalOpLookup *fc_la = (const ExprEvalOpLookup *) fc_a;
	const ExprEvalOpLookup *fc_lb = (const ExprEvalOpLookup *) fc_b;

	if (fc_la->opcode < fc_lb->opcode)
		return -1;
	else if (fc_la->opcode > fc_lb->opcode)
		return 1;
	return 0;
}
#endif

/*
 * 进行一次性初始化的解释机制。
 */
static void fc_ExecInitInterpreter(void)
{
#if defined(EEO_USE_COMPUTED_GOTO)
	/* 设置对外可见的调度表指针 */
	if (dispatch_table == NULL)
	{
		dispatch_table = (const void **)
			DatumGetPointer(fc_ExecInterpExpr(NULL, NULL, NULL));

		/*构建反向查找表 */
		for (int fc_i = 0; fc_i < EEOP_LAST; fc_i++)
		{
			reverse_dispatch_table[fc_i].opcode = dispatch_table[fc_i];
			reverse_dispatch_table[fc_i].op = (ExprEvalOp) fc_i;
		}

		/*使其可供bsearch()使用 */
		qsort(reverse_dispatch_table,
			  EEOP_LAST /* nmembers */ ,
			  sizeof(ExprEvalOpLookup),
			  fc_dispatch_compare_ptr);
	}
#endif
}

/*
 * 返回表达式步骤的操作码的函数。
 *
 * 当使用直接线程时，ExprState->opcode不易被解读。
 * 此函数返回适当的枚举成员。
 */
ExprEvalOp
ExecEvalStepOp(ExprState *fc_state, ExprEvalStep *fc_op)
{
#if defined(EEO_USE_COMPUTED_GOTO)
	if (fc_state->flags & EEO_FLAG_DIRECT_THREADED)
	{
		ExprEvalOpLookup fc_key;
		ExprEvalOpLookup *fc_res;

		fc_key.opcode = (void *) fc_op->opcode;
		fc_res = bsearch(&fc_key,
					  reverse_dispatch_table,
					  EEOP_LAST /* nmembers */ ,
					  sizeof(ExprEvalOpLookup),
					  fc_dispatch_compare_ptr);
		Assert(fc_res);			/*未知操作不应该被查找 */
		return fc_res->op;
	}
#endif
	return (ExprEvalOp) fc_op->opcode;
}


/*
 * 复杂指令的离线帮助函数。
 */

/*
 * 评估EEOP_FUNCEXPR_FUSAGE
 */
void ExecEvalFuncExprFusage(ExprState *fc_state, ExprEvalStep *fc_op,
					   ExprContext *fc_econtext)
{
	FunctionCallInfo fcinfo = fc_op->d.func.fcinfo_data;
	PgStat_FunctionCallUsage fc_fcusage;
	Datum		fc_d;

	pgstat_init_function_usage(fcinfo, &fc_fcusage);

	fcinfo->isnull = false;
	fc_d = fc_op->d.func.fn_addr(fcinfo);
	*fc_op->resvalue = fc_d;
	*fc_op->resnull = fcinfo->isnull;

	pgstat_end_function_usage(&fc_fcusage, true);
}

/*
 * 评估EEOP_FUNCEXPR_STRICT_FUSAGE
 */
void ExecEvalFuncExprStrictFusage(ExprState *fc_state, ExprEvalStep *fc_op,
							 ExprContext *fc_econtext)
{

	FunctionCallInfo fcinfo = fc_op->d.func.fcinfo_data;
	PgStat_FunctionCallUsage fc_fcusage;
	NullableDatum *fc_args = fcinfo->args;
	int			fc_nargs = fc_op->d.func.nargs;
	Datum		fc_d;

	/* 严格函数，因此检查 NULL 参数 */
	for (int fc_argno = 0; fc_argno < fc_nargs; fc_argno++)
	{
		if (fc_args[fc_argno].isnull)
		{
			*fc_op->resnull = true;
			return;
		}
	}

	pgstat_init_function_usage(fcinfo, &fc_fcusage);

	fcinfo->isnull = false;
	fc_d = fc_op->d.func.fn_addr(fcinfo);
	*fc_op->resvalue = fc_d;
	*fc_op->resnull = fcinfo->isnull;

	pgstat_end_function_usage(&fc_fcusage, true);
}

/*
 * 评估PARAM_EXEC参数。
 *
 * PARAM_EXEC参数（内部执行器参数）存储在
 * ecxt_param_exec_vals数组中，可以通过数组索引访问。
 */
void ExecEvalParamExec(ExprState *fc_state, ExprEvalStep *fc_op, ExprContext *fc_econtext)
{
	ParamExecData *fc_prm;

	fc_prm = &(fc_econtext->ecxt_param_exec_vals[fc_op->d.param.paramid]);
	if (unlikely(fc_prm->execPlan != NULL))
	{
		/*参数尚未评估，所以去做它 */
		ExecSetParamPlan(fc_prm->execPlan, fc_econtext);
		/*ExecSetParamPlan应该已经处理了这个参数... */
		Assert(fc_prm->execPlan == NULL);
	}
	*fc_op->resvalue = fc_prm->value;
	*fc_op->resnull = fc_prm->isnull;
}

/*
 * 评估PARAM_EXTERN参数。
 *
 * 必须在ecxt_param_list_info中查找PARAM_EXTERN参数。
 */
void ExecEvalParamExtern(ExprState *fc_state, ExprEvalStep *fc_op, ExprContext *fc_econtext)
{
	ParamListInfo fc_paramInfo = fc_econtext->ecxt_param_list_info;
	int			fc_paramId = fc_op->d.param.paramid;

	if (likely(fc_paramInfo &&
			   fc_paramId > 0 && fc_paramId <= fc_paramInfo->numParams))
	{
		ParamExternData *fc_prm;
		ParamExternData fc_prmdata;

		/* 给钩子一个机会，以防参数是动态的 */
		if (fc_paramInfo->paramFetch != NULL)
			fc_prm = fc_paramInfo->paramFetch(fc_paramInfo, fc_paramId, false, &fc_prmdata);
		else
			fc_prm = &fc_paramInfo->params[fc_paramId - 1];

		if (likely(OidIsValid(fc_prm->ptype)))
		{
			/* 安全检查，以防钩子做了意外的事情 */
			if (unlikely(fc_prm->ptype != fc_op->d.param.paramtype))
				ereport(ERROR,
						(errcode(ERRCODE_DATATYPE_MISMATCH),
						 errmsg("type of parameter %d (%s) does not match that when preparing the plan (%s)",
								fc_paramId,
								format_type_be(fc_prm->ptype),
								format_type_be(fc_op->d.param.paramtype))));
			*fc_op->resvalue = fc_prm->value;
			*fc_op->resnull = fc_prm->isnull;
			return;
		}
	}

	ereport(ERROR,
			(errcode(ERRCODE_UNDEFINED_OBJECT),
			 errmsg("no value found for parameter %d", fc_paramId)));
}

/*
 * 评估SQLValueFunction表达式。
 */
void ExecEvalSQLValueFunction(ExprState *fc_state, ExprEvalStep *fc_op)
{
	LOCAL_FCINFO(fcinfo, 0);
	SQLValueFunction *fc_svf = fc_op->d.sqlvaluefunction.svf;

	*fc_op->resnull = false;

	/*
	 * 注意：current_schema()可能返回NULL。current_user()等当前
	 * 不能返回NULL，但为了安全起见，最好还是以相同方式编写这些情况。
	 */
	switch (fc_svf->op)
	{
		case SVFOP_CURRENT_DATE:
			*fc_op->resvalue = DateADTGetDatum(GetSQLCurrentDate());
			break;
		case SVFOP_CURRENT_TIME:
		case SVFOP_CURRENT_TIME_N:
			*fc_op->resvalue = TimeTzADTPGetDatum(GetSQLCurrentTime(fc_svf->typmod));
			break;
		case SVFOP_CURRENT_TIMESTAMP:
		case SVFOP_CURRENT_TIMESTAMP_N:
			*fc_op->resvalue = TimestampTzGetDatum(GetSQLCurrentTimestamp(fc_svf->typmod));
			break;
		case SVFOP_LOCALTIME:
		case SVFOP_LOCALTIME_N:
			*fc_op->resvalue = TimeADTGetDatum(GetSQLLocalTime(fc_svf->typmod));
			break;
		case SVFOP_LOCALTIMESTAMP:
		case SVFOP_LOCALTIMESTAMP_N:
			*fc_op->resvalue = TimestampGetDatum(GetSQLLocalTimestamp(fc_svf->typmod));
			break;
		case SVFOP_CURRENT_ROLE:
		case SVFOP_CURRENT_USER:
		case SVFOP_USER:
			InitFunctionCallInfoData(*fcinfo, NULL, 0, InvalidOid, NULL, NULL);
			*fc_op->resvalue = current_user(fcinfo);
			*fc_op->resnull = fcinfo->isnull;
			break;
		case SVFOP_SESSION_USER:
			InitFunctionCallInfoData(*fcinfo, NULL, 0, InvalidOid, NULL, NULL);
			*fc_op->resvalue = session_user(fcinfo);
			*fc_op->resnull = fcinfo->isnull;
			break;
		case SVFOP_CURRENT_CATALOG:
			InitFunctionCallInfoData(*fcinfo, NULL, 0, InvalidOid, NULL, NULL);
			*fc_op->resvalue = current_database(fcinfo);
			*fc_op->resnull = fcinfo->isnull;
			break;
		case SVFOP_CURRENT_SCHEMA:
			InitFunctionCallInfoData(*fcinfo, NULL, 0, InvalidOid, NULL, NULL);
			*fc_op->resvalue = current_schema(fcinfo);
			*fc_op->resnull = fcinfo->isnull;
			break;
	}
}

/*
 * 如果评估CURRENT OF表达式，则抛出错误。
 *
 * 规划器应该将CURRENT OF转换为TidScan资格，或在ForeignScan节点中进行某种
 * 其他特殊处理。因此我们必须能够对CurrentOfExpr进行ExecInitExpr，
 * 但我们不应该真正执行它。如果到这里，我们假设我们必须处理
 * FOREIGN TABLE的CURRENT OF，该表的FDW不处理它，并相应地抱怨。
 */
void ExecEvalCurrentOfExpr(ExprState *fc_state, ExprEvalStep *fc_op)
{
	ereport(ERROR,
			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
			 errmsg("WHERE CURRENT OF is not supported for this table type")));
}

/*
 * 评估NextValueExpr。
 */
void ExecEvalNextValueExpr(ExprState *fc_state, ExprEvalStep *fc_op)
{
	int64		fc_newval = nextval_internal(fc_op->d.nextvalueexpr.seqid, false);

	switch (fc_op->d.nextvalueexpr.seqtypid)
	{
		case INT2OID:
			*fc_op->resvalue = Int16GetDatum((int16) fc_newval);
			break;
		case INT4OID:
			*fc_op->resvalue = Int32GetDatum((int32) fc_newval);
			break;
		case INT8OID:
			*fc_op->resvalue = Int64GetDatum((int64) fc_newval);
			break;
		default:
			elog(ERROR, "unsupported sequence type %u",
				 fc_op->d.nextvalueexpr.seqtypid);
	}
	*fc_op->resnull = false;
}

/*
 * 评估NullTest / IS NULL对于行。
 */
void ExecEvalRowNull(ExprState *fc_state, ExprEvalStep *fc_op, ExprContext *fc_econtext)
{
	fc_ExecEvalRowNullInt(fc_state, fc_op, fc_econtext, true);
}

/*
 * 评估NullTest / IS NOT NULL对于行。
 */
void ExecEvalRowNotNull(ExprState *fc_state, ExprEvalStep *fc_op, ExprContext *fc_econtext)
{
	fc_ExecEvalRowNullInt(fc_state, fc_op, fc_econtext, false);
}

/* 行值上的IS [NOT] NULL的通用代码 */
static void fc_ExecEvalRowNullInt(ExprState *fc_state, ExprEvalStep *fc_op,
				   ExprContext *fc_econtext, bool fc_checkisnull)
{
	Datum		fc_value = *fc_op->resvalue;
	bool		fc_isnull = *fc_op->resnull;
	HeapTupleHeader fc_tuple;
	Oid			fc_tupType;
	int32		fc_tupTypmod;
	TupleDesc	fc_tupDesc;
	HeapTupleData fc_tmptup;

	*fc_op->resnull = false;

	/* NULL行变量与NULL标量列相同对待 */
	if (fc_isnull)
	{
		*fc_op->resvalue = BoolGetDatum(fc_checkisnull);
		return;
	}

	/*
	 * SQL标准将IS [NOT] NULL对于非空行类型参数定义为：
	 *
	 * "R IS NULL" 如果每个字段都是空值，则为真。
	 *
	 * "R IS NOT NULL" 如果没有字段是空值，则为真。
	 *
	 * 此定义（显然是故意的）不是递归的；因此我们
	 * 对字段的测试是原始的attisnull测试，而不是递归检查
	 * 它们是否都是空值或没有空值的行类型。
	 *
	 * 标准未考虑零字段行的可能性，但
	 * 在这里我们认为它们空泛地满足这两个谓词。
	 */

	fc_tuple = DatumGetHeapTupleHeader(fc_value);

	fc_tupType = HeapTupleHeaderGetTypeId(fc_tuple);
	fc_tupTypmod = HeapTupleHeaderGetTypMod(fc_tuple);

	/* 第一次通过或类型更改时查找tupdesc */
	fc_tupDesc = get_cached_rowtype(fc_tupType, fc_tupTypmod,
								 &fc_op->d.nulltest_row.rowcache, NULL);

	/*
	 * heap_attisnull需要HeapTuple而不是裸HeapTupleHeader。
	 */
	fc_tmptup.t_len = HeapTupleHeaderGetDatumLength(fc_tuple);
	fc_tmptup.t_data = fc_tuple;

	for (int fc_att = 1; fc_att <= fc_tupDesc->natts; fc_att++)
	{
		/* 忽略已删除的列 */
		if (TupleDescAttr(fc_tupDesc, fc_att - 1)->attisdropped)
			continue;
		if (heap_attisnull(&fc_tmptup, fc_att, fc_tupDesc))
		{
			/* 空字段驳斥IS NOT NULL */
			if (!fc_checkisnull)
			{
				*fc_op->resvalue = BoolGetDatum(false);
				return;
			}
		}
		else
		{
			/* 非空字段驳斥IS NULL */
			if (fc_checkisnull)
			{
				*fc_op->resvalue = BoolGetDatum(false);
				return;
			}
		}
	}

	*fc_op->resvalue = BoolGetDatum(true);
}

/*
 * 评估ARRAY[]表达式。
 *
 * 单个数组元素（或子数组）已经被评估
 * 到op->d.arrayexpr.elemvalues[]/elemnulls[]中。
 */
void ExecEvalArrayExpr(ExprState *fc_state, ExprEvalStep *fc_op)
{
	ArrayType  *fc_result;
	Oid			fc_element_type = fc_op->d.arrayexpr.elemtype;
	int			fc_nelems = fc_op->d.arrayexpr.nelems;
	int			fc_ndims = 0;
	int			fc_dims[MAXDIM];
	int			fc_lbs[MAXDIM];

	/* 将非空设为默认值 */
	*fc_op->resnull = false;

	if (!fc_op->d.arrayexpr.multidims)
	{
		/* 元素推测为标量类型 */
		Datum	   *fc_dvalues = fc_op->d.arrayexpr.elemvalues;
		bool	   *fc_dnulls = fc_op->d.arrayexpr.elemnulls;

		/* 为给定长度的1-D数组设置 */
		fc_ndims = 1;
		fc_dims[0] = fc_nelems;
		fc_lbs[0] = 1;

		fc_result = construct_md_array(fc_dvalues, fc_dnulls, fc_ndims, fc_dims, fc_lbs,
									fc_element_type,
									fc_op->d.arrayexpr.elemlength,
									fc_op->d.arrayexpr.elembyval,
									fc_op->d.arrayexpr.elemalign);
	}
	else
	{
		/* 必须是嵌套数组表达式 */
		int			fc_nbytes = 0;
		int			fc_nitems;
		int			fc_outer_nelems = 0;
		int			fc_elem_ndims = 0;
		int		   *fc_elem_dims = NULL;
		int		   *fc_elem_lbs = NULL;
		bool		fc_firstone = true;
		bool		fc_havenulls = false;
		bool		fc_haveempty = false;
		char	  **fc_subdata;
		bits8	  **fc_subbitmaps;
		int		   *fc_subbytes;
		int		   *fc_subnitems;
		int32		fc_dataoffset;
		char	   *fc_dat;
		int			fc_iitem;

		fc_subdata = (char **) palloc(fc_nelems * sizeof(char *));
		fc_subbitmaps = (bits8 **) palloc(fc_nelems * sizeof(bits8 *));
		fc_subbytes = (int *) palloc(fc_nelems * sizeof(int));
		fc_subnitems = (int *) palloc(fc_nelems * sizeof(int));

		/* 循环遍历并从每个元素获取数据区域 */
		for (int fc_elemoff = 0; fc_elemoff < fc_nelems; fc_elemoff++)
		{
			Datum		fc_arraydatum;
			bool		fc_eisnull;
			ArrayType  *fc_array;
			int			fc_this_ndims;

			fc_arraydatum = fc_op->d.arrayexpr.elemvalues[fc_elemoff];
			fc_eisnull = fc_op->d.arrayexpr.elemnulls[fc_elemoff];

			/* 暂时忽略空的子数组 */
			if (fc_eisnull)
			{
				fc_haveempty = true;
				continue;
			}

			fc_array = DatumGetArrayTypeP(fc_arraydatum);

			/* 运行时对元素类型进行双重检查 */
			if (fc_element_type != ARR_ELEMTYPE(fc_array))
				ereport(ERROR,
						(errcode(ERRCODE_DATATYPE_MISMATCH),
						 errmsg("cannot merge incompatible arrays"),
						 errdetail("Array with element type %s cannot be "
								   "included in ARRAY construct with element type %s.",
								   format_type_be(ARR_ELEMTYPE(fc_array)),
								   format_type_be(fc_element_type))));

			fc_this_ndims = ARR_NDIM(fc_array);
			/* 暂时忽略零维子数组 */
			if (fc_this_ndims <= 0)
			{
				fc_haveempty = true;
				continue;
			}

			if (fc_firstone)
			{
				/* 从第一个成员获取子数组详细信息 */
				fc_elem_ndims = fc_this_ndims;
				fc_ndims = fc_elem_ndims + 1;
				if (fc_ndims <= 0 || fc_ndims > MAXDIM)
					ereport(ERROR,
							(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
							 errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
									fc_ndims, MAXDIM)));

				fc_elem_dims = (int *) palloc(fc_elem_ndims * sizeof(int));
				memcpy(fc_elem_dims, ARR_DIMS(fc_array), fc_elem_ndims * sizeof(int));
				fc_elem_lbs = (int *) palloc(fc_elem_ndims * sizeof(int));
				memcpy(fc_elem_lbs, ARR_LBOUND(fc_array), fc_elem_ndims * sizeof(int));

				fc_firstone = false;
			}
			else
			{
				/* 检查其他子数组是否兼容 */
				if (fc_elem_ndims != fc_this_ndims ||
					memcmp(fc_elem_dims, ARR_DIMS(fc_array),
						   fc_elem_ndims * sizeof(int)) != 0 ||
					memcmp(fc_elem_lbs, ARR_LBOUND(fc_array),
						   fc_elem_ndims * sizeof(int)) != 0)
					ereport(ERROR,
							(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
							 errmsg("multidimensional arrays must have array "
									"expressions with matching dimensions")));
			}

			fc_subdata[fc_outer_nelems] = ARR_DATA_PTR(fc_array);
			fc_subbitmaps[fc_outer_nelems] = ARR_NULLBITMAP(fc_array);
			fc_subbytes[fc_outer_nelems] = ARR_SIZE(fc_array) - ARR_DATA_OFFSET(fc_array);
			fc_nbytes += fc_subbytes[fc_outer_nelems];
			/* 检查总请求的溢出 */
			if (!AllocSizeIsValid(fc_nbytes))
				ereport(ERROR,
						(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
						 errmsg("array size exceeds the maximum allowed (%d)",
								(int) MaxAllocSize)));
			fc_subnitems[fc_outer_nelems] = ArrayGetNItems(fc_this_ndims,
													 ARR_DIMS(fc_array));
			fc_havenulls |= ARR_HASNULL(fc_array);
			fc_outer_nelems++;
		}

		/*
		 * 如果所有项目都是空或空数组，返回一个空数组；
		 * 否则，如果有些是空的而有些不是，则引发错误。 （注意：我们
		 * 必须以某种方式特殊处理这一点，以避免尝试生成一个由空数组形成的 1-D
		 * 数组。这并不理想……）
*/
		if (fc_haveempty)
		{
			if (fc_ndims == 0)		/* 没找到任何非空数组 */
			{
				*fc_op->resvalue = PointerGetDatum(construct_empty_array(fc_element_type));
				return;
			}
			ereport(ERROR,
					(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
					 errmsg("multidimensional arrays must have array "
							"expressions with matching dimensions")));
		}

		/* 为多维数组设置 */
		fc_dims[0] = fc_outer_nelems;
		fc_lbs[0] = 1;
		for (int fc_i = 1; fc_i < fc_ndims; fc_i++)
		{
			fc_dims[fc_i] = fc_elem_dims[fc_i - 1];
			fc_lbs[fc_i] = fc_elem_lbs[fc_i - 1];
		}

		/* 检查下标溢出 */
		fc_nitems = ArrayGetNItems(fc_ndims, fc_dims);
		ArrayCheckBounds(fc_ndims, fc_dims, fc_lbs);

		if (fc_havenulls)
		{
			fc_dataoffset = ARR_OVERHEAD_WITHNULLS(fc_ndims, fc_nitems);
			fc_nbytes += fc_dataoffset;
		}
		else
		{
			fc_dataoffset = 0;		/* 表示没有空位图的标记 */
			fc_nbytes += ARR_OVERHEAD_NONULLS(fc_ndims);
		}

		fc_result = (ArrayType *) palloc0(fc_nbytes);
		SET_VARSIZE(fc_result, fc_nbytes);
		fc_result->ndim = fc_ndims;
		fc_result->dataoffset = fc_dataoffset;
		fc_result->elemtype = fc_element_type;
		memcpy(ARR_DIMS(fc_result), fc_dims, fc_ndims * sizeof(int));
		memcpy(ARR_LBOUND(fc_result), fc_lbs, fc_ndims * sizeof(int));

		fc_dat = ARR_DATA_PTR(fc_result);
		fc_iitem = 0;
		for (int fc_i = 0; fc_i < fc_outer_nelems; fc_i++)
		{
			memcpy(fc_dat, fc_subdata[fc_i], fc_subbytes[fc_i]);
			fc_dat += fc_subbytes[fc_i];
			if (fc_havenulls)
				array_bitmap_copy(ARR_NULLBITMAP(fc_result), fc_iitem,
								  fc_subbitmaps[fc_i], 0,
								  fc_subnitems[fc_i]);
			fc_iitem += fc_subnitems[fc_i];
		}
	}

	*fc_op->resvalue = PointerGetDatum(fc_result);
}

/*
 * 评估 ArrayCoerceExpr 表达式。
 *
 * 源数组在步骤的结果变量中。
*/
void ExecEvalArrayCoerce(ExprState *fc_state, ExprEvalStep *fc_op, ExprContext *fc_econtext)
{
	Datum		fc_arraydatum;

	/* NULL 数组 -> NULL 结果 */
	if (*fc_op->resnull)
		return;

	fc_arraydatum = *fc_op->resvalue;

	/*
	 * 如果它是二进制兼容的，则修改数组头中的元素类型，
	 * 否则保持我们收到的数组不变。
*/
	if (fc_op->d.arraycoerce.elemexprstate == NULL)
	{
		/* 如有必要，对输入数组进行解压，并在任何情况下进行复制 */
		ArrayType  *fc_array = DatumGetArrayTypePCopy(fc_arraydatum);

		ARR_ELEMTYPE(fc_array) = fc_op->d.arraycoerce.resultelemtype;
		*fc_op->resvalue = PointerGetDatum(fc_array);
		return;
	}

	/*
	 * 使用 array_map 将子表达式应用于每个数组元素。
*/
	*fc_op->resvalue = array_map(fc_arraydatum,
							  fc_op->d.arraycoerce.elemexprstate,
							  fc_econtext,
							  fc_op->d.arraycoerce.resultelemtype,
							  fc_op->d.arraycoerce.amstate);
}

/*
 * 评估 ROW() 表达式。
 *
 * 各个列已经被评估到
 * op->d.row.elemvalues[]/elemnulls[] 中。
*/
void ExecEvalRow(ExprState *fc_state, ExprEvalStep *fc_op)
{
	HeapTuple	fc_tuple;

	/* 从评估后的字段值构建元组 */
	fc_tuple = heap_form_tuple(fc_op->d.row.tupdesc,
							fc_op->d.row.elemvalues,
							fc_op->d.row.elemnulls);

	*fc_op->resvalue = HeapTupleGetDatum(fc_tuple);
	*fc_op->resnull = false;
}

/*
 * 评估 GREATEST() 或 LEAST() 表达式（注意这不是 MIN()/MAX()）。
 *
 * 所有需要比较的表达式已经被评估到
 * op->d.minmax.values[]/nulls[] 中。
*/
void ExecEvalMinMax(ExprState *fc_state, ExprEvalStep *fc_op)
{
	Datum	   *fc_values = fc_op->d.minmax.values;
	bool	   *fc_nulls = fc_op->d.minmax.nulls;
	FunctionCallInfo fcinfo = fc_op->d.minmax.fcinfo_data;
	MinMaxOp	fc_operator = fc_op->d.minmax.op;

	/* 初始化时设置 */
	Assert(fcinfo->args[0].isnull == false);
	Assert(fcinfo->args[1].isnull == false);

	/* 默认为 NULL 结果 */
	*fc_op->resnull = true;

	for (int fc_off = 0; fc_off < fc_op->d.minmax.nelems; fc_off++)
	{
		/* 忽略 NULL 输入 */
		if (fc_nulls[fc_off])
			continue;

		if (*fc_op->resnull)
		{
			/* 第一个非空输入，采用值 */
			*fc_op->resvalue = fc_values[fc_off];
			*fc_op->resnull = false;
		}
		else
		{
			int			fc_cmpresult;

			/* 应用比较函数 */
			fcinfo->args[0].value = *fc_op->resvalue;
			fcinfo->args[1].value = fc_values[fc_off];

			fcinfo->isnull = false;
			fc_cmpresult = DatumGetInt32(FunctionCallInvoke(fcinfo));
			if (fcinfo->isnull) /* 这可能不应该发生 */
				continue;

			if (fc_cmpresult > 0 && fc_operator == IS_LEAST)
				*fc_op->resvalue = fc_values[fc_off];
			else if (fc_cmpresult < 0 && fc_operator == IS_GREATEST)
				*fc_op->resvalue = fc_values[fc_off];
		}
	}
}

/*
 * 评估 FieldSelect 节点。
 *
 * 源记录在步骤的结果变量中。
*/
void ExecEvalFieldSelect(ExprState *fc_state, ExprEvalStep *fc_op, ExprContext *fc_econtext)
{
	AttrNumber	fc_fieldnum = fc_op->d.fieldselect.fieldnum;
	Datum		fc_tupDatum;
	HeapTupleHeader fc_tuple;
	Oid			fc_tupType;
	int32		fc_tupTypmod;
	TupleDesc	fc_tupDesc;
	Form_pg_attribute fc_attr;
	HeapTupleData fc_tmptup;

	/* NULL 记录 -> NULL 结果 */
	if (*fc_op->resnull)
		return;

	fc_tupDatum = *fc_op->resvalue;

	/* 我们可以为扩展记录特例化以提高速度 */
	if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(fc_tupDatum)))
	{
		ExpandedRecordHeader *fc_erh = (ExpandedRecordHeader *) DatumGetEOHP(fc_tupDatum);

		Assert(fc_erh->er_magic == ER_MAGIC);

		/* 提取记录的 TupleDesc */
		fc_tupDesc = expanded_record_get_tupdesc(fc_erh);

		/*
		 * 查找字段的属性记录。 注意我们在这里不支持系统列：
		 * 数据元组对于大多数有趣的系统列没有有效值。
*/
		if (fc_fieldnum <= 0)		/* 永远不应该发生 */
			elog(ERROR, "unsupported reference to system column %d in FieldSelect",
				 fc_fieldnum);
		if (fc_fieldnum > fc_tupDesc->natts)	/* 永远不应该发生 */
			elog(ERROR, "attribute number %d exceeds number of columns %d",
				 fc_fieldnum, fc_tupDesc->natts);
		fc_attr = TupleDescAttr(fc_tupDesc, fc_fieldnum - 1);

		/* 检查是否丢弃的列，如果是，则强制返回 NULL 结果 */
		if (fc_attr->attisdropped)
		{
			*fc_op->resnull = true;
			return;
		}

		/* 检查类型不匹配 —— 在 ALTER COLUMN TYPE 之后可能发生？ */
		/* 如同 CheckVarSlotCompatibility 中，我们应该但不能检查 typmod */
		if (fc_op->d.fieldselect.resulttype != fc_attr->atttypid)
			ereport(ERROR,
					(errcode(ERRCODE_DATATYPE_MISMATCH),
					 errmsg("attribute %d has wrong type", fc_fieldnum),
					 errdetail("Table has type %s, but query expects %s.",
							   format_type_be(fc_attr->atttypid),
							   format_type_be(fc_op->d.fieldselect.resulttype))));

		/* 提取字段 */
		*fc_op->resvalue = expanded_record_get_field(fc_erh, fc_fieldnum,
												  fc_op->resnull);
	}
	else
	{
		/* 获取复合数据并提取其类型字段 */
		fc_tuple = DatumGetHeapTupleHeader(fc_tupDatum);

		fc_tupType = HeapTupleHeaderGetTypeId(fc_tuple);
		fc_tupTypmod = HeapTupleHeaderGetTypMod(fc_tuple);

		/* 第一次通过或类型更改时查找tupdesc */
		fc_tupDesc = get_cached_rowtype(fc_tupType, fc_tupTypmod,
									 &fc_op->d.fieldselect.rowcache, NULL);

		/*
		 * 查找字段的属性记录。 注意我们在这里不支持系统列：
		 * 数据元组对于大多数有趣的系统列没有有效值。
*/
		if (fc_fieldnum <= 0)		/* 永远不应该发生 */
			elog(ERROR, "unsupported reference to system column %d in FieldSelect",
				 fc_fieldnum);
		if (fc_fieldnum > fc_tupDesc->natts)	/* 永远不应该发生 */
			elog(ERROR, "attribute number %d exceeds number of columns %d",
				 fc_fieldnum, fc_tupDesc->natts);
		fc_attr = TupleDescAttr(fc_tupDesc, fc_fieldnum - 1);

		/* 检查是否丢弃的列，如果是，则强制返回 NULL 结果 */
		if (fc_attr->attisdropped)
		{
			*fc_op->resnull = true;
			return;
		}

		/* 检查类型不匹配 —— 在 ALTER COLUMN TYPE 之后可能发生？ */
		/* 如同 CheckVarSlotCompatibility 中，我们应该但不能检查 typmod */
		if (fc_op->d.fieldselect.resulttype != fc_attr->atttypid)
			ereport(ERROR,
					(errcode(ERRCODE_DATATYPE_MISMATCH),
					 errmsg("attribute %d has wrong type", fc_fieldnum),
					 errdetail("Table has type %s, but query expects %s.",
							   format_type_be(fc_attr->atttypid),
							   format_type_be(fc_op->d.fieldselect.resulttype))));

		/* heap_getattr 需要一个 HeapTuple 而不是一个裸 HeapTupleHeader */
		fc_tmptup.t_len = HeapTupleHeaderGetDatumLength(fc_tuple);
		fc_tmptup.t_data = fc_tuple;

		/* 提取字段 */
		*fc_op->resvalue = heap_getattr(&fc_tmptup,
									 fc_fieldnum,
									 fc_tupDesc,
									 fc_op->resnull);
	}
}

/*
 * 变形源元组，填充步骤的值/空值数组，之后评估作为 FieldStore 表达式的一部分的各个新值。
 * 后续步骤将用新的字段值覆盖值/空值数组的各个元素，然后 FIELDSTORE_FORM 将构建新的元组值。
 *
 * 源记录在步骤的结果变量中。
 */
void ExecEvalFieldStoreDeForm(ExprState *fc_state, ExprEvalStep *fc_op, ExprContext *fc_econtext)
{
	if (*fc_op->resnull)
	{
		/* 将空输入元组转换为全空行 */
		memset(fc_op->d.fieldstore.nulls, true,
			   fc_op->d.fieldstore.ncolumns * sizeof(bool));
	}
	else
	{
		/*
		 * heap_deform_tuple 需要一个 HeapTuple 而不是一个裸 HeapTupleHeader。我们
		 * 在结构中设置所有字段以防万一。
		 */
		Datum		fc_tupDatum = *fc_op->resvalue;
		HeapTupleHeader fc_tuphdr;
		HeapTupleData fc_tmptup;
		TupleDesc	fc_tupDesc;

		fc_tuphdr = DatumGetHeapTupleHeader(fc_tupDatum);
		fc_tmptup.t_len = HeapTupleHeaderGetDatumLength(fc_tuphdr);
		ItemPointerSetInvalid(&(fc_tmptup.t_self));
		fc_tmptup.t_tableOid = InvalidOid;
		fc_tmptup.t_data = fc_tuphdr;

		/*
		 * 如果第一次通过或类型更改，则查找 tupdesc。因为
		 * 我们不固定 tupdesc，所以必须等到调用 DatumGetHeapTupleHeader 后再进行此查找：在 
		 * 解压数据时可能会访问数据库。
		 */
		fc_tupDesc = get_cached_rowtype(fc_op->d.fieldstore.fstore->resulttype, -1,
									 fc_op->d.fieldstore.rowcache, NULL);

		/* 检查当前 tupdesc 是否没有比分配的字段更多的字段 */
		if (unlikely(fc_tupDesc->natts > fc_op->d.fieldstore.ncolumns))
			elog(ERROR, "too many columns in composite type %u",
				 fc_op->d.fieldstore.fstore->resulttype);

		heap_deform_tuple(&fc_tmptup, fc_tupDesc,
						  fc_op->d.fieldstore.values,
						  fc_op->d.fieldstore.nulls);
	}
}

/*
 * 在每个 FieldStore 表达式的各个字段值评估后计算新的复合数据。
 */
void ExecEvalFieldStoreForm(ExprState *fc_state, ExprEvalStep *fc_op, ExprContext *fc_econtext)
{
	TupleDesc	fc_tupDesc;
	HeapTuple	fc_tuple;

	/* 查找 tupdesc（应该已经有效） */
	fc_tupDesc = get_cached_rowtype(fc_op->d.fieldstore.fstore->resulttype, -1,
								 fc_op->d.fieldstore.rowcache, NULL);

	fc_tuple = heap_form_tuple(fc_tupDesc,
							fc_op->d.fieldstore.values,
							fc_op->d.fieldstore.nulls);

	*fc_op->resvalue = HeapTupleGetDatum(fc_tuple);
	*fc_op->resnull = false;
}

/*
 * 评估行类型强制转换操作。
 * 这可能需要重新排列字段位置。
 *
 * 源记录在步骤的结果变量中。
 */
void ExecEvalConvertRowtype(ExprState *fc_state, ExprEvalStep *fc_op, ExprContext *fc_econtext)
{
	HeapTuple	fc_result;
	Datum		fc_tupDatum;
	HeapTupleHeader fc_tuple;
	HeapTupleData fc_tmptup;
	TupleDesc	fc_indesc,
				fc_outdesc;
	bool		fc_changed = false;

	/* NULL 输入 -> NULL 输出 */
	if (*fc_op->resnull)
		return;

	fc_tupDatum = *fc_op->resvalue;
	fc_tuple = DatumGetHeapTupleHeader(fc_tupDatum);

	/*
	 * 如果第一次通过或类型更改，则查找 tupdescs。我们最好
	 * 锁定它们，因为类型转换函数可能会进行目录查找，从而导致缓存失效。
	 */
	fc_indesc = get_cached_rowtype(fc_op->d.convert_rowtype.inputtype, -1,
								fc_op->d.convert_rowtype.incache,
								&fc_changed);
	IncrTupleDescRefCount(fc_indesc);
	fc_outdesc = get_cached_rowtype(fc_op->d.convert_rowtype.outputtype, -1,
								 fc_op->d.convert_rowtype.outcache,
								 &fc_changed);
	IncrTupleDescRefCount(fc_outdesc);

	/*
	 * 我们过去可以断言传入元组标记为
	 * 与 indesc 完全相同的行类型。然而，现在 ExecEvalWholeRowVar
	 * 可能会由于插入别名而将元组的标记更改为普通 RECORD，因此我们只能做这个弱测试：
	 */
	Assert(HeapTupleHeaderGetTypeId(fc_tuple) == fc_indesc->tdtypeid ||
		   HeapTupleHeaderGetTypeId(fc_tuple) == RECORDOID);

	/* 如果第一次通过，或在更改后，初始化转换映射 */
	if (fc_changed)
	{
		MemoryContext fc_old_cxt;

		/* 在长期存在的内存上下文中分配映射 */
		fc_old_cxt = MemoryContextSwitchTo(fc_econtext->ecxt_per_query_memory);

		/* 准备从旧属性编号到新属性编号的映射 */
		fc_op->d.convert_rowtype.map = convert_tuples_by_name(fc_indesc, fc_outdesc);

		MemoryContextSwitchTo(fc_old_cxt);
	}

	/* 后续步骤需要 HeapTuple 而不是裸 HeapTupleHeader */
	fc_tmptup.t_len = HeapTupleHeaderGetDatumLength(fc_tuple);
	fc_tmptup.t_data = fc_tuple;

	if (fc_op->d.convert_rowtype.map != NULL)
	{
		/* 需要进行属性重排的完全转换 */
		fc_result = execute_attr_map_tuple(&fc_tmptup, fc_op->d.convert_rowtype.map);
		/* 结果已经具有适当的复合数据头字段 */
		*fc_op->resvalue = HeapTupleGetDatum(fc_result);
	}
	else
	{
		/*
		 * 元组在物理上是兼容的，但我们需要在其复合数据头字段中插入
		 * 目标行类型 OID，因此我们必须根据情况进行复制。heap_copy_tuple_as_datum() 对此很方便，
		 * 因为它将进行物理复制并插入正确的复合头字段。请注意，我们并不指望
		 * 需要扁平化任何经过压缩的字段：输入是一个复合数据，因此不应包含任何。
		 * 所以 heap_copy_tuple_as_datum() 在这里有些多余，但它对外部字段的检查是廉价的。
		 */
		*fc_op->resvalue = heap_copy_tuple_as_datum(&fc_tmptup, fc_outdesc);
	}

	DecrTupleDescRefCount(fc_indesc);
	DecrTupleDescRefCount(fc_outdesc);
}

/*
 * 评估 "标量操作 ANY/ALL (数组)"。
 *
 * 源数组在我们的结果区域，标量参数已经评估到
 * fcinfo->args[0]。
 *
 * 该操作符总是返回布尔值，我们使用 OR 和 AND （分别用于 ANY 和 ALL）结合所有数组元素的结果。当然，
 * 我们在结果已知时会立即短路。
 */
void ExecEvalScalarArrayOp(ExprState *fc_state, ExprEvalStep *fc_op)
{
	FunctionCallInfo fcinfo = fc_op->d.scalararrayop.fcinfo_data;
	bool		fc_useOr = fc_op->d.scalararrayop.useOr;
	bool		fc_strictfunc = fc_op->d.scalararrayop.finfo->fn_strict;
	ArrayType  *fc_arr;
	int			fc_nitems;
	Datum		fc_result;
	bool		fc_resultnull;
	int16		fc_typlen;
	bool		fc_typbyval;
	char		fc_typalign;
	char	   *fc_s;
	bits8	   *fc_bitmap;
	int			fc_bitmask;

	/*
	 * 如果数组为 NULL，则返回 NULL --- 不做任何其他事情并没有
	 * 多大意义，即使操作符并不严格。
	 */
	if (*fc_op->resnull)
		return;

	/* 否则可以获取和解压数组 */
	fc_arr = DatumGetArrayTypeP(*fc_op->resvalue);

	/*
	 * 如果数组为空，我们根据 useOr 标志返回 FALSE 或 TRUE。
	 * 即使标量为 NULL，这也是正确的；因为我们将评估该操作符零次，返回 NULL
	 * 与否并无大碍。
	 */
	fc_nitems = ArrayGetNItems(ARR_NDIM(fc_arr), ARR_DIMS(fc_arr));
	if (fc_nitems <= 0)
	{
		*fc_op->resvalue = BoolGetDatum(!fc_useOr);
		*fc_op->resnull = false;
		return;
	}

	/*
	 * 如果标量为 NULL，并且函数是严格的，则返回 NULL；没有必要遍历循环。
	 */
	if (fcinfo->args[0].isnull && fc_strictfunc)
	{
		*fc_op->resnull = true;
		return;
	}

	/*
	 * 我们安排在一系列调用中仅查找一次元素类型的信息，假设元素类型在我们下面不变。
	 */
	if (fc_op->d.scalararrayop.element_type != ARR_ELEMTYPE(fc_arr))
	{
		get_typlenbyvalalign(ARR_ELEMTYPE(fc_arr),
							 &fc_op->d.scalararrayop.typlen,
							 &fc_op->d.scalararrayop.typbyval,
							 &fc_op->d.scalararrayop.typalign);
		fc_op->d.scalararrayop.element_type = ARR_ELEMTYPE(fc_arr);
	}

	fc_typlen = fc_op->d.scalararrayop.typlen;
	fc_typbyval = fc_op->d.scalararrayop.typbyval;
	fc_typalign = fc_op->d.scalararrayop.typalign;

	/* 根据 useOr 适当地初始化结果 */
	fc_result = BoolGetDatum(!fc_useOr);
	fc_resultnull = false;

	/* 循环遍历数组元素 */
	fc_s = (char *) ARR_DATA_PTR(fc_arr);
	fc_bitmap = ARR_NULLBITMAP(fc_arr);
	fc_bitmask = 1;

	for (int fc_i = 0; fc_i < fc_nitems; fc_i++)
	{
		Datum		fc_elt;
		Datum		fc_thisresult;

		/* 获取数组元素，检查是否为 NULL */
		if (fc_bitmap && (*fc_bitmap & fc_bitmask) == 0)
		{
			fcinfo->args[1].value = (Datum) 0;
			fcinfo->args[1].isnull = true;
		}
		else
		{
			fc_elt = fetch_att(fc_s, fc_typbyval, fc_typlen);
			fc_s = att_addlength_pointer(fc_s, fc_typlen, fc_s);
			fc_s = (char *) att_align_nominal(fc_s, fc_typalign);
			fcinfo->args[1].value = fc_elt;
			fcinfo->args[1].isnull = false;
		}

		/* 调用比较函数 */
		if (fcinfo->args[1].isnull && fc_strictfunc)
		{
			fcinfo->isnull = true;
			fc_thisresult = (Datum) 0;
		}
		else
		{
			fcinfo->isnull = false;
			fc_thisresult = fc_op->d.scalararrayop.fn_addr(fcinfo);
		}

		/* 根据 OR 或 AND 语义合并结果 */
		if (fcinfo->isnull)
			fc_resultnull = true;
		else if (fc_useOr)
		{
			if (DatumGetBool(fc_thisresult))
			{
				fc_result = BoolGetDatum(true);
				fc_resultnull = false;
				break;			/* 不需要再查看其他元素 */
			}
		}
		else
		{
			if (!DatumGetBool(fc_thisresult))
			{
				fc_result = BoolGetDatum(false);
				fc_resultnull = false;
				break;			/* 不需要再查看其他元素 */
			}
		}

		/* 如果有，向前推进位图指针 */
		if (fc_bitmap)
		{
			fc_bitmask <<= 1;
			if (fc_bitmask == 0x100)
			{
				fc_bitmap++;
				fc_bitmask = 1;
			}
		}
	}

	*fc_op->resvalue = fc_result;
	*fc_op->resnull = fc_resultnull;
}

/*
 * 供标量数组哈希操作元素使用的哈希函数。
 *
 * 我们使用元素类型的默认哈希操作类，并且如果类型对排序敏感，则使用列排序。
 */
static uint32 fc_saop_element_hash(struct saophash_hash *fc_tb, Datum fc_key)
{
	ScalarArrayOpExprHashTable *fc_elements_tab = (ScalarArrayOpExprHashTable *) fc_tb->private_data;
	FunctionCallInfo fcinfo = &fc_elements_tab->hash_fcinfo_data;
	Datum		fc_hash;

	fcinfo->args[0].value = fc_key;
	fcinfo->args[0].isnull = false;

	fc_hash = fc_elements_tab->hash_finfo.fn_addr(fcinfo);

	return DatumGetUInt32(fc_hash);
}

/*
 * 用于标量数组哈希操作元素的匹配函数，用于哈希表查找。
 */
static bool fc_saop_hash_element_match(struct saophash_hash *fc_tb, Datum fc_key1, Datum fc_key2)
{
	Datum		fc_result;

	ScalarArrayOpExprHashTable *fc_elements_tab = (ScalarArrayOpExprHashTable *) fc_tb->private_data;
	FunctionCallInfo fcinfo = fc_elements_tab->op->d.hashedscalararrayop.fcinfo_data;

	fcinfo->args[0].value = fc_key1;
	fcinfo->args[0].isnull = false;
	fcinfo->args[1].value = fc_key2;
	fcinfo->args[1].isnull = false;

	fc_result = fc_elements_tab->op->d.hashedscalararrayop.finfo->fn_addr(fcinfo);

	return DatumGetBool(fc_result);
}

/*
 * 评估 "标量操作 ANY (常量数组)"。
 *
 * 类似于 ExecEvalScalarArrayOp，但通过首次查找构建哈希表来优化快速重复查找。
 * 此哈希表将在后续查找中重用。与 ExecEvalScalarArrayOp 不同，此版本仅支持 OR 语义。
 *
 * 源数组位于我们的结果区域，标量参数已评估为 fcinfo->args[0]。
 *
 * 该运算符始终返回布尔值。
 */
void ExecEvalHashedScalarArrayOp(ExprState *fc_state, ExprEvalStep *fc_op, ExprContext *fc_econtext)
{
	ScalarArrayOpExprHashTable *fc_elements_tab = fc_op->d.hashedscalararrayop.elements_tab;
	FunctionCallInfo fcinfo = fc_op->d.hashedscalararrayop.fcinfo_data;
	bool		fc_inclause = fc_op->d.hashedscalararrayop.inclause;
	bool		fc_strictfunc = fc_op->d.hashedscalararrayop.finfo->fn_strict;
	Datum		fc_scalar = fcinfo->args[0].value;
	bool		fc_scalar_isnull = fcinfo->args[0].isnull;
	Datum		fc_result;
	bool		fc_resultnull;
	bool		fc_hashfound;

	/* 如果数组常量为 null，则我们不设置哈希标量数组操作。 */
	Assert(!*fc_op->resnull);

	/*
	 * 如果标量为 NULL，并且函数是严格的，则返回 NULL；没有
	 * 执行搜索的意义。
	 */
	if (fcinfo->args[0].isnull && fc_strictfunc)
	{
		*fc_op->resnull = true;
		return;
	}

	/* 在首次评估时构建哈希表 */
	if (fc_elements_tab == NULL)
	{
		ScalarArrayOpExpr *fc_saop;
		int16		fc_typlen;
		bool		fc_typbyval;
		char		fc_typalign;
		int			fc_nitems;
		bool		fc_has_nulls = false;
		char	   *fc_s;
		bits8	   *fc_bitmap;
		int			fc_bitmask;
		MemoryContext fc_oldcontext;
		ArrayType  *fc_arr;

		fc_saop = fc_op->d.hashedscalararrayop.saop;

		fc_arr = DatumGetArrayTypeP(*fc_op->resvalue);
		fc_nitems = ArrayGetNItems(ARR_NDIM(fc_arr), ARR_DIMS(fc_arr));

		get_typlenbyvalalign(ARR_ELEMTYPE(fc_arr),
							 &fc_typlen,
							 &fc_typbyval,
							 &fc_typalign);

		fc_oldcontext = MemoryContextSwitchTo(fc_econtext->ecxt_per_query_memory);

		fc_elements_tab = (ScalarArrayOpExprHashTable *)
			palloc0(offsetof(ScalarArrayOpExprHashTable, hash_fcinfo_data) +
					SizeForFunctionCallInfo(1));
		fc_op->d.hashedscalararrayop.elements_tab = fc_elements_tab;
		fc_elements_tab->op = fc_op;

		fmgr_info(fc_saop->hashfuncid, &fc_elements_tab->hash_finfo);
		fmgr_info_set_expr((Node *) fc_saop, &fc_elements_tab->hash_finfo);

		InitFunctionCallInfoData(fc_elements_tab->hash_fcinfo_data,
								 &fc_elements_tab->hash_finfo,
								 1,
								 fc_saop->inputcollid,
								 NULL,
								 NULL);

		/*
		 * 根据数组中的元素数量创建哈希表并调整大小。
		 * 这假设数组没有重复项。
		 * 如果数组恰好包含许多重复值，那么这只意味着我们将表的大小设置得大了一点。
		 */
		fc_elements_tab->hashtab = saophash_create(CurrentMemoryContext, fc_nitems,
												fc_elements_tab);

		MemoryContextSwitchTo(fc_oldcontext);

		fc_s = (char *) ARR_DATA_PTR(fc_arr);
		fc_bitmap = ARR_NULLBITMAP(fc_arr);
		fc_bitmask = 1;
		for (int fc_i = 0; fc_i < fc_nitems; fc_i++)
		{
			/* 获取数组元素，检查是否为 NULL。 */
			if (fc_bitmap && (*fc_bitmap & fc_bitmask) == 0)
			{
				fc_has_nulls = true;
			}
			else
			{
				Datum		fc_element;

				fc_element = fetch_att(fc_s, fc_typbyval, fc_typlen);
				fc_s = att_addlength_pointer(fc_s, fc_typlen, fc_s);
				fc_s = (char *) att_align_nominal(fc_s, fc_typalign);

				saophash_insert(fc_elements_tab->hashtab, fc_element, &fc_hashfound);
			}

			/* 如果有任何，向前推进位图指针。 */
			if (fc_bitmap)
			{
				fc_bitmask <<= 1;
				if (fc_bitmask == 0x100)
				{
					fc_bitmap++;
					fc_bitmask = 1;
				}
			}
		}

		/*
		 * 记住我们是否有任何 null，以便在没有找到匹配时知道
		 * 是否需要以 null lhs 值执行非严格函数。
		 */
		fc_op->d.hashedscalararrayop.has_nulls = fc_has_nulls;
	}

	/* 检查哈希以查看我们是否有匹配。 */
	fc_hashfound = NULL != saophash_lookup(fc_elements_tab->hashtab, fc_scalar);

	/* 结果取决于子句是 IN 还是 NOT IN 子句 */
	if (fc_inclause)
		fc_result = BoolGetDatum(fc_hashfound);	/* 在 */
	else
		fc_result = BoolGetDatum(!fc_hashfound);	/* NOT IN */

	fc_resultnull = false;

	/*
	 * 如果我们在数组中没有找到匹配项，我们仍然可能需要处理
	 * null 值的可能性。我们没有将任何 NULL 放入哈希表中，但在构建表时标记了是否找到任何
	 * 有哈希 null。
	 */
	if (!fc_hashfound && fc_op->d.hashedscalararrayop.has_nulls)
	{
		if (fc_strictfunc)
		{

			/*
			 * 数组中有 null，因此非 null lhs 没有匹配必须
			 * 产生 NULL。
			 */
			fc_result = (Datum) 0;
			fc_resultnull = true;
		}
		else
		{
			/*
			 * 执行功能将 null rhs 只执行一次。
			 *
			 * 哈希查找路径将在 lhs 参数上留下痕迹，因此
			 * 我们还需要进行设置（即使我们已经设置了此函数）。
			 */
			fcinfo->args[0].value = fc_scalar;
			fcinfo->args[0].isnull = fc_scalar_isnull;
			fcinfo->args[1].value = (Datum) 0;
			fcinfo->args[1].isnull = true;

			fc_result = fc_op->d.hashedscalararrayop.finfo->fn_addr(fcinfo);
			fc_resultnull = fcinfo->isnull;

			/*
			 * 反转 NOT IN 子句的结果，因为上述函数
			 * 是等式函数，我们需要不相等。
			 */
			if (!fc_inclause)
				fc_result = !fc_result;
		}
	}

	*fc_op->resvalue = fc_result;
	*fc_op->resnull = fc_resultnull;
}

/*
 * 评估 NOT NULL 域约束。
 */
void ExecEvalConstraintNotNull(ExprState *fc_state, ExprEvalStep *fc_op)
{
	if (*fc_op->resnull)
		ereport(ERROR,
				(errcode(ERRCODE_NOT_NULL_VIOLATION),
				 errmsg("domain %s does not allow null values",
						format_type_be(fc_op->d.domaincheck.resulttype)),
				 errdatatype(fc_op->d.domaincheck.resulttype)));
}

/*
 * 评估 CHECK 域约束。
 */
void ExecEvalConstraintCheck(ExprState *fc_state, ExprEvalStep *fc_op)
{
	if (!*fc_op->d.domaincheck.checknull &&
		!DatumGetBool(*fc_op->d.domaincheck.checkvalue))
		ereport(ERROR,
				(errcode(ERRCODE_CHECK_VIOLATION),
				 errmsg("value for domain %s violates check constraint \"%s\"",
						format_type_be(fc_op->d.domaincheck.resulttype),
						fc_op->d.domaincheck.constraintname),
				 errdomainconstraint(fc_op->d.domaincheck.resulttype,
									 fc_op->d.domaincheck.constraintname)));
}

/*
 * 评估 XmlExpr 的各种形式。
 *
 * 参数已经被评估为 named_argvalue/named_argnull
 * 和/或 argvalue/argnull 数组。
 */
void ExecEvalXmlExpr(ExprState *fc_state, ExprEvalStep *fc_op)
{
	XmlExpr    *fc_xexpr = fc_op->d.xmlexpr.xexpr;
	Datum		fc_value;

	*fc_op->resnull = true;		/* 直到我们得到结果 */
	*fc_op->resvalue = (Datum) 0;

	switch (fc_xexpr->op)
	{
		case IS_XMLCONCAT:
			{
				Datum	   *fc_argvalue = fc_op->d.xmlexpr.argvalue;
				bool	   *fc_argnull = fc_op->d.xmlexpr.argnull;
				List	   *fc_values = NIL;

				for (int fc_i = 0; fc_i < list_length(fc_xexpr->args); fc_i++)
				{
					if (!fc_argnull[fc_i])
						fc_values = lappend(fc_values, DatumGetPointer(fc_argvalue[fc_i]));
				}

				if (fc_values != NIL)
				{
					*fc_op->resvalue = PointerGetDatum(xmlconcat(fc_values));
					*fc_op->resnull = false;
				}
			}
			break;

		case IS_XMLFOREST:
			{
				Datum	   *fc_argvalue = fc_op->d.xmlexpr.named_argvalue;
				bool	   *fc_argnull = fc_op->d.xmlexpr.named_argnull;
				StringInfoData fc_buf;
				ListCell   *fc_lc;
				ListCell   *fc_lc2;
				int			fc_i;

				initStringInfo(&fc_buf);

				fc_i = 0;
				forboth(fc_lc, fc_xexpr->named_args, fc_lc2, fc_xexpr->arg_names)
				{
					Expr	   *fc_e = (Expr *) lfirst(fc_lc);
					char	   *fc_argname = strVal(lfirst(fc_lc2));

					if (!fc_argnull[fc_i])
					{
						fc_value = fc_argvalue[fc_i];
						appendStringInfo(&fc_buf, "<%s>%s</%s>",
										 fc_argname,
										 map_sql_value_to_xml_value(fc_value,
																	exprType((Node *) fc_e), true),
										 fc_argname);
						*fc_op->resnull = false;
					}
					fc_i++;
				}

				if (!*fc_op->resnull)
				{
					text	   *fc_result;

					fc_result = cstring_to_text_with_len(fc_buf.data, fc_buf.len);
					*fc_op->resvalue = PointerGetDatum(fc_result);
				}

				pfree(fc_buf.data);
			}
			break;

		case IS_XMLELEMENT:
			*fc_op->resvalue = PointerGetDatum(xmlelement(fc_xexpr,
													   fc_op->d.xmlexpr.named_argvalue,
													   fc_op->d.xmlexpr.named_argnull,
													   fc_op->d.xmlexpr.argvalue,
													   fc_op->d.xmlexpr.argnull));
			*fc_op->resnull = false;
			break;

		case IS_XMLPARSE:
			{
				Datum	   *fc_argvalue = fc_op->d.xmlexpr.argvalue;
				bool	   *fc_argnull = fc_op->d.xmlexpr.argnull;
				text	   *fc_data;
				bool		fc_preserve_whitespace;

				/* 参数已知为文本、布尔值 */
				Assert(list_length(fc_xexpr->args) == 2);

				if (fc_argnull[0])
					return;
				fc_value = fc_argvalue[0];
				fc_data = DatumGetTextPP(fc_value);

				if (fc_argnull[1]) /* 列表中的语法错误 */
					return;
				fc_value = fc_argvalue[1];
				fc_preserve_whitespace = DatumGetBool(fc_value);

				*fc_op->resvalue = PointerGetDatum(xmlparse(fc_data,
														 fc_xexpr->xmloption,
														 fc_preserve_whitespace));
				*fc_op->resnull = false;
			}
			break;

		case IS_XMLPI:
			{
				text	   *fc_arg;
				bool		fc_isnull;

				/* 可选参数已知为文本 */
				Assert(list_length(fc_xexpr->args) <= 1);

				if (fc_xexpr->args)
				{
					fc_isnull = fc_op->d.xmlexpr.argnull[0];
					if (fc_isnull)
						fc_arg = NULL;
					else
						fc_arg = DatumGetTextPP(fc_op->d.xmlexpr.argvalue[0]);
				}
				else
				{
					fc_arg = NULL;
					fc_isnull = false;
				}

				*fc_op->resvalue = PointerGetDatum(xmlpi(fc_xexpr->name,
													  fc_arg,
													  fc_isnull,
													  fc_op->resnull));
			}
			break;

		case IS_XMLROOT:
			{
				Datum	   *fc_argvalue = fc_op->d.xmlexpr.argvalue;
				bool	   *fc_argnull = fc_op->d.xmlexpr.argnull;
				xmltype    *fc_data;
				text	   *fc_version;
				int			fc_standalone;

				/* 参数已知为 xml、文本、整数 */
				Assert(list_length(fc_xexpr->args) == 3);

				if (fc_argnull[0])
					return;
				fc_data = DatumGetXmlP(fc_argvalue[0]);

				if (fc_argnull[1])
					fc_version = NULL;
				else
					fc_version = DatumGetTextPP(fc_argvalue[1]);

				Assert(!fc_argnull[2]);	/* 始终存在 */
				fc_standalone = DatumGetInt32(fc_argvalue[2]);

				*fc_op->resvalue = PointerGetDatum(xmlroot(fc_data,
														fc_version,
														fc_standalone));
				*fc_op->resnull = false;
			}
			break;

		case IS_XMLSERIALIZE:
			{
				Datum	   *fc_argvalue = fc_op->d.xmlexpr.argvalue;
				bool	   *fc_argnull = fc_op->d.xmlexpr.argnull;

				/* 参数类型已知为 xml */
				Assert(list_length(fc_xexpr->args) == 1);

				if (fc_argnull[0])
					return;
				fc_value = fc_argvalue[0];

				*fc_op->resvalue = PointerGetDatum(xmltotext_with_xmloption(DatumGetXmlP(fc_value),
																		 fc_xexpr->xmloption));
				*fc_op->resnull = false;
			}
			break;

		case IS_DOCUMENT:
			{
				Datum	   *fc_argvalue = fc_op->d.xmlexpr.argvalue;
				bool	   *fc_argnull = fc_op->d.xmlexpr.argnull;

				/* 可选参数已知为 xml */
				Assert(list_length(fc_xexpr->args) == 1);

				if (fc_argnull[0])
					return;
				fc_value = fc_argvalue[0];

				*fc_op->resvalue =
					BoolGetDatum(xml_is_document(DatumGetXmlP(fc_value)));
				*fc_op->resnull = false;
			}
			break;

		default:
			elog(ERROR, "unrecognized XML operation");
			break;
	}
}

/*
 * ExecEvalGroupingFunc
 *
 * 计算一个位掩码，每个（未评估的）参数表达式对应一个位
 * （最右边的参数是最低有效位）。
 *
 * 如果相应表达式不是当前分组集中分组表达式的部分，则设置相应的位。
 */
void ExecEvalGroupingFunc(ExprState *fc_state, ExprEvalStep *fc_op)
{
	AggState   *fc_aggstate = castNode(AggState, fc_state->parent);
	int			fc_result = 0;
	Bitmapset  *fc_grouped_cols = fc_aggstate->grouped_cols;
	ListCell   *fc_lc;

	foreach(fc_lc, fc_op->d.grouping_func.clauses)
	{
		int			fc_attnum = lfirst_int(fc_lc);

		fc_result <<= 1;

		if (!bms_is_member(fc_attnum, fc_grouped_cols))
			fc_result |= 1;
	}

	*fc_op->resvalue = Int32GetDatum(fc_result);
	*fc_op->resnull = false;
}

/*
 * 将子计划的评估移交给 nodeSubplan.c
 */
void ExecEvalSubPlan(ExprState *fc_state, ExprEvalStep *fc_op, ExprContext *fc_econtext)
{
	SubPlanState *fc_sstate = fc_op->d.subplan.sstate;

	/* 可能会嵌套，因此确保有足够的栈空间 */
	check_stack_depth();

	*fc_op->resvalue = ExecSubPlan(fc_sstate, fc_econtext, fc_op->resnull);
}

/*
 * 评估一个整行 Var 表达式。
 *
 * 返回一个 Datum，其值是与给定表达式上下文相关的整行范围变量的值。
 */
void ExecEvalWholeRowVar(ExprState *fc_state, ExprEvalStep *fc_op, ExprContext *fc_econtext)
{
	Var		   *fc_variable = fc_op->d.wholerow.var;
	TupleTableSlot *fc_slot;
	TupleDesc	fc_output_tupdesc;
	MemoryContext fc_oldcontext;
	HeapTupleHeader fc_dtuple;
	HeapTuple	fc_tuple;

	/* 这是通过 ExecInitExpr 检查的 */
	Assert(fc_variable->varattno == InvalidAttrNumber);

	/* 获取我们想要的输入插槽 */
	switch (fc_variable->varno)
	{
		case INNER_VAR:
			/* 从内部节点获取元组 */
			fc_slot = fc_econtext->ecxt_innertuple;
			break;

		case OUTER_VAR:
			/* 从外部节点获取元组 */
			fc_slot = fc_econtext->ecxt_outertuple;
			break;

			/* INDEX_VAR 由默认情况处理 */

		default:
			/* 从正在扫描的关系中获取元组 */
			fc_slot = fc_econtext->ecxt_scantuple;
			break;
	}

	/* 如有必要，应用垃圾过滤器 */
	if (fc_op->d.wholerow.junkFilter != NULL)
		fc_slot = ExecFilterJunk(fc_op->d.wholerow.junkFilter, fc_slot);

	/*
	 * 如果是第一次循环，获取元组描述符并检查兼容性。
	 *
	 * XXX：如果这可以移至表达式初始化阶段，那就太好了，但由于使用插槽，目前不可行。
	 */
	if (fc_op->d.wholerow.first)
	{
		/* 乐观地假设我们不需要慢路径 */
		fc_op->d.wholerow.slow = false;

		/*
		 * 如果 Var 标识一个命名的复合类型，我们必须检查实际元组类型是否与之兼容。
		 */
		if (fc_variable->vartype != RECORDOID)
		{
			TupleDesc	fc_var_tupdesc;
			TupleDesc	fc_slot_tupdesc;

			/*
			 * 我们只关心属性的数量和数据类型。
			 * 此外，只要（1）物理存储匹配或（2）实际列值为 NULL，我们可以忽略在目标类型中删除的列的类型不匹配。
			 * 情况（1）对涉及过时缓存计划的某些情况很有帮助，而情况（2）在诸如插入
			 * 到一个删除了列的表中的情况下是预期行为（计划器通常会生成一个 INT4 NULL，而不论被删除的列类型是什么）。
			 * 如果我们发现一个删除的列并且无法验证情况（1）成立，我们必须使用慢路径检查每一行的（2）。
			 *
			 * 如果 vartype 是复合类型上的域，只需查找该域以获取基本复合类型。
			 */
			fc_var_tupdesc = lookup_rowtype_tupdesc_domain(fc_variable->vartype,
														-1, false);

			fc_slot_tupdesc = fc_slot->tts_tupleDescriptor;

			if (fc_var_tupdesc->natts != fc_slot_tupdesc->natts)
				ereport(ERROR,
						(errcode(ERRCODE_DATATYPE_MISMATCH),
						 errmsg("table row type and query-specified row type do not match"),
						 errdetail_plural("Table row contains %d attribute, but query expects %d.",
										  "Table row contains %d attributes, but query expects %d.",
										  fc_slot_tupdesc->natts,
										  fc_slot_tupdesc->natts,
										  fc_var_tupdesc->natts)));

			for (int fc_i = 0; fc_i < fc_var_tupdesc->natts; fc_i++)
			{
				Form_pg_attribute fc_vattr = TupleDescAttr(fc_var_tupdesc, fc_i);
				Form_pg_attribute fc_sattr = TupleDescAttr(fc_slot_tupdesc, fc_i);

				if (fc_vattr->atttypid == fc_sattr->atttypid)
					continue;	/* 不用担心 */
				if (!fc_vattr->attisdropped)
					ereport(ERROR,
							(errcode(ERRCODE_DATATYPE_MISMATCH),
							 errmsg("table row type and query-specified row type do not match"),
							 errdetail("Table has type %s at ordinal position %d, but query expects %s.",
									   format_type_be(fc_sattr->atttypid),
									   fc_i + 1,
									   format_type_be(fc_vattr->atttypid))));

				if (fc_vattr->attlen != fc_sattr->attlen ||
					fc_vattr->attalign != fc_sattr->attalign)
					fc_op->d.wholerow.slow = true; /* 需要检查 NULL */
			}

			/*
			 * 使用变量声明的行类型作为输出值的描述符。特别是，我们 *必须* 吸收任何
			 * attisdropped 标记。
			 */
			fc_oldcontext = MemoryContextSwitchTo(fc_econtext->ecxt_per_query_memory);
			fc_output_tupdesc = CreateTupleDescCopy(fc_var_tupdesc);
			MemoryContextSwitchTo(fc_oldcontext);

			ReleaseTupleDesc(fc_var_tupdesc);
		}
		else
		{
			/*
			 * 在 RECORD 情况下，我们使用输入插槽的行类型作为
			 * 输出值的描述符，可能会在下面分配新的列名。
			 */
			fc_oldcontext = MemoryContextSwitchTo(fc_econtext->ecxt_per_query_memory);
			fc_output_tupdesc = CreateTupleDescCopy(fc_slot->tts_tupleDescriptor);
			MemoryContextSwitchTo(fc_oldcontext);

			/*
			 * 输入插槽可能是一个关系扫描插槽，因此被标记为该关系的行类型。
			 * 但我们应该返回 RECORD，因此将其重置为此。
			 */
			fc_output_tupdesc->tdtypeid = RECORDOID;
			fc_output_tupdesc->tdtypmod = -1;

			/*
			 * 我们已经在上面获取了正确的物理数据类型信息，但
			 * 现在我们应该尝试找到源 RTE 并采用其列别名，因为输入插槽不太可能具有
			 * 期望的名称。
			 *
			 * 如果我们无法找到 RTE，假设我们拥有的列名是可以的。
			 * （截至本文撰写时，我们无法找到 RTE 的唯一情况是在触发器 WHEN 子句的执行中， 
			 * 那时 Var 将具有触发器的关系的行类型，因此其名称是可以的。）此外，如果 RTE 的创建者
			 * 没有填写 eref 字段，假设我们的列名是可以的。（这发生在 COPY 中，或许在其他地方。）
			 */
			if (fc_econtext->ecxt_estate &&
				fc_variable->varno <= fc_econtext->ecxt_estate->es_range_table_size)
			{
				RangeTblEntry *fc_rte = exec_rt_fetch(fc_variable->varno,
												   fc_econtext->ecxt_estate);

				if (fc_rte->eref)
					ExecTypeSetColNames(fc_output_tupdesc, fc_rte->eref->colnames);
			}
		}

		/* 如有必要，祝福 tupdesc，并将其保存在执行状态中 */
		fc_op->d.wholerow.tupdesc = BlessTupleDesc(fc_output_tupdesc);

		fc_op->d.wholerow.first = false;
	}

	/*
	 * 确保插槽的所有列在插槽的 Datum/isnull 数组中都是可访问的。
	 */
	slot_getallattrs(fc_slot);

	if (fc_op->d.wholerow.slow)
	{
		/* 检查是否有任何删除的属性为非 NULL */
		TupleDesc	fc_tupleDesc = fc_slot->tts_tupleDescriptor;
		TupleDesc	fc_var_tupdesc = fc_op->d.wholerow.tupdesc;

		Assert(fc_var_tupdesc->natts == fc_tupleDesc->natts);

		for (int fc_i = 0; fc_i < fc_var_tupdesc->natts; fc_i++)
		{
			Form_pg_attribute fc_vattr = TupleDescAttr(fc_var_tupdesc, fc_i);
			Form_pg_attribute fc_sattr = TupleDescAttr(fc_tupleDesc, fc_i);

			if (!fc_vattr->attisdropped)
				continue;		/* 已经检查了未删除的列 */
			if (fc_slot->tts_isnull[fc_i])
				continue;		/* NULL 总是可以 */
			if (fc_vattr->attlen != fc_sattr->attlen ||
				fc_vattr->attalign != fc_sattr->attalign)
				ereport(ERROR,
						(errcode(ERRCODE_DATATYPE_MISMATCH),
						 errmsg("table row type and query-specified row type do not match"),
						 errdetail("Physical storage mismatch on dropped attribute at ordinal position %d.",
								   fc_i + 1)));
		}
	}

	/*
	 * 构建一个复合 Datum，确保任何滴水字段被去滴水处理。
	 *
	 * （注意：关键是我们不能在这里更改插槽的状态。）
	 */
	fc_tuple = toast_build_flattened_tuple(fc_slot->tts_tupleDescriptor,
										fc_slot->tts_values,
										fc_slot->tts_isnull);
	fc_dtuple = fc_tuple->t_data;

	/*
	 * 用我们之前识别的复合类型信息标记 Datum。
	 *
	 * （注意：我们可以通过将 op->d.wholerow.tupdesc 传递给
	 * 元组构建步骤来跳过此操作；但这似乎有些冒险，所以我们不这样做。）
	 */
	HeapTupleHeaderSetTypeId(fc_dtuple, fc_op->d.wholerow.tupdesc->tdtypeid);
	HeapTupleHeaderSetTypMod(fc_dtuple, fc_op->d.wholerow.tupdesc->tdtypmod);

	*fc_op->resvalue = PointerGetDatum(fc_dtuple);
	*fc_op->resnull = false;
}

void ExecEvalSysVar(ExprState *fc_state, ExprEvalStep *fc_op, ExprContext *fc_econtext,
			   TupleTableSlot *fc_slot)
{
	Datum		fc_d;

	/* slot_getsysattr 对坏的 attnums 有足够的防护措施 */
	fc_d = slot_getsysattr(fc_slot,
						fc_op->d.var.attnum,
						fc_op->resnull);
	*fc_op->resvalue = fc_d;
	/* 这应该是不可达的，但检查一下也无妨 */
	if (unlikely(*fc_op->resnull))
		elog(ERROR, "failed to fetch attribute from slot");
}

/*
 * 过渡值尚未初始化。这是一个组的第一个非 NULL 输入
 * 值。我们将其用作 transValue 的初始值。
 */
void ExecAggInitGroup(AggState *fc_aggstate, AggStatePerTrans fc_pertrans, AggStatePerGroup fc_pergroup,
				 ExprContext *fc_aggcontext)
{
	FunctionCallInfo fcinfo = fc_pertrans->transfn_fcinfo;
	MemoryContext fc_oldContext;

	/*
	 * 如果 datum 是按引用传递的，我们必须将其复制到 aggcontext。我们不需要 pfree 旧的 transValue，
	 * 因为它是 NULL。（我们已经检查过 agg 的输入类型与其 transtype 是二进制兼容的，因此
	 * 在这里直接复制是可以的。）
	 */
	fc_oldContext = MemoryContextSwitchTo(fc_aggcontext->ecxt_per_tuple_memory);
	fc_pergroup->transValue = datumCopy(fcinfo->args[1].value,
									 fc_pertrans->transtypeByVal,
									 fc_pertrans->transtypeLen);
	fc_pergroup->transValueIsNull = false;
	fc_pergroup->noTransValue = false;
	MemoryContextSwitchTo(fc_oldContext);
}

/*
 * 确保当前转变值是 aggcontext 的子项，而不是每个元组的上下文。
 *
 * 注意：这可能会更改当前内存上下文。
 */
Datum ExecAggTransReparent(AggState *fc_aggstate, AggStatePerTrans fc_pertrans,
					 Datum fc_newValue, bool fc_newValueIsNull,
					 Datum fc_oldValue, bool fc_oldValueIsNull)
{
	Assert(fc_newValue != fc_oldValue);

	if (!fc_newValueIsNull)
	{
		MemoryContextSwitchTo(fc_aggstate->curaggcontext->ecxt_per_tuple_memory);
		if (DatumIsReadWriteExpandedObject(fc_newValue,
										   false,
										   fc_pertrans->transtypeLen) &&
			MemoryContextGetParent(DatumGetEOHP(fc_newValue)->eoh_context) == CurrentMemoryContext)
			 /* 不执行任何操作 */ ;
		else
			fc_newValue = datumCopy(fc_newValue,
								 fc_pertrans->transtypeByVal,
								 fc_pertrans->transtypeLen);
	}
	else
	{
		/*
		 * 确保 AggStatePerGroup->transValue 最终为 0，以便
		 * 调用者可以安全地比较 newValue/oldValue，而无需检查它们各自的 NULL 状态。
		 */
		fc_newValue = (Datum) 0;
	}

	if (!fc_oldValueIsNull)
	{
		if (DatumIsReadWriteExpandedObject(fc_oldValue,
										   false,
										   fc_pertrans->transtypeLen))
			DeleteExpandedObject(fc_oldValue);
		else
			pfree(DatumGetPointer(fc_oldValue));
	}

	return fc_newValue;
}

/*
 * 调用有序转变函数，参数为 datum。
 */
void ExecEvalAggOrderedTransDatum(ExprState *fc_state, ExprEvalStep *fc_op,
							 ExprContext *fc_econtext)
{
	AggStatePerTrans fc_pertrans = fc_op->d.agg_trans.pertrans;
	int			fc_setno = fc_op->d.agg_trans.setno;

	tuplesort_putdatum(fc_pertrans->sortstates[fc_setno],
					   *fc_op->resvalue, *fc_op->resnull);
}

/*
 * 调用有序转换函数，带有元组参数。
 */
void ExecEvalAggOrderedTransTuple(ExprState *fc_state, ExprEvalStep *fc_op,
							 ExprContext *fc_econtext)
{
	AggStatePerTrans fc_pertrans = fc_op->d.agg_trans.pertrans;
	int			fc_setno = fc_op->d.agg_trans.setno;

	ExecClearTuple(fc_pertrans->sortslot);
	fc_pertrans->sortslot->tts_nvalid = fc_pertrans->numInputs;
	ExecStoreVirtualTuple(fc_pertrans->sortslot);
	tuplesort_puttupleslot(fc_pertrans->sortstates[fc_setno], fc_pertrans->sortslot);
}

/* 针对按值类型的转换函数调用的实现 */
static pg_attribute_always_inline void fc_ExecAggPlainTransByVal(AggState *fc_aggstate, AggStatePerTrans fc_pertrans,
					   AggStatePerGroup fc_pergroup,
					   ExprContext *fc_aggcontext, int fc_setno)
{
	FunctionCallInfo fcinfo = fc_pertrans->transfn_fcinfo;
	MemoryContext fc_oldContext;
	Datum		fc_newVal;

	/* cf. select_current_set() */
	fc_aggstate->curaggcontext = fc_aggcontext;
	fc_aggstate->current_set = fc_setno;

	/* 为 AggGetAggref() 设置 aggstate->curpertrans */
	fc_aggstate->curpertrans = fc_pertrans;

	/* 在每个元组上下文中调用转换函数 */
	fc_oldContext = MemoryContextSwitchTo(fc_aggstate->tmpcontext->ecxt_per_tuple_memory);

	fcinfo->args[0].value = fc_pergroup->transValue;
	fcinfo->args[0].isnull = fc_pergroup->transValueIsNull;
	fcinfo->isnull = false;		/* 以防 transfn 不设置它 */

	fc_newVal = FunctionCallInvoke(fcinfo);

	fc_pergroup->transValue = fc_newVal;
	fc_pergroup->transValueIsNull = fcinfo->isnull;

	MemoryContextSwitchTo(fc_oldContext);
}

/* 针对按引用类型的转换函数调用的实现 */
static pg_attribute_always_inline void fc_ExecAggPlainTransByRef(AggState *fc_aggstate, AggStatePerTrans fc_pertrans,
					   AggStatePerGroup fc_pergroup,
					   ExprContext *fc_aggcontext, int fc_setno)
{
	FunctionCallInfo fcinfo = fc_pertrans->transfn_fcinfo;
	MemoryContext fc_oldContext;
	Datum		fc_newVal;

	/* cf. select_current_set() */
	fc_aggstate->curaggcontext = fc_aggcontext;
	fc_aggstate->current_set = fc_setno;

	/* 为 AggGetAggref() 设置 aggstate->curpertrans */
	fc_aggstate->curpertrans = fc_pertrans;

	/* 在每个元组上下文中调用转换函数 */
	fc_oldContext = MemoryContextSwitchTo(fc_aggstate->tmpcontext->ecxt_per_tuple_memory);

	fcinfo->args[0].value = fc_pergroup->transValue;
	fcinfo->args[0].isnull = fc_pergroup->transValueIsNull;
	fcinfo->isnull = false;		/* 以防 transfn 不设置它 */

	fc_newVal = FunctionCallInvoke(fcinfo);

	/*
	 * 对于按引用传递的数据类型，必须将新值复制到 aggcontext 中并
	 * 释放先前的 transValue。但是如果 transfn 返回了其
	 * 第一个输入的指针，我们就不需要做任何事情。同时，如果 transfn 返回了
	 * 一个已经是 aggcontext 子对象的 R/W 扩展对象的指针，假设我们可以
	 * 在不复制它的情况下采用该值。
	 *
	 * 在比较 newVal 和 pergroup->transValue 时，它们都不需要是 NULL，
	 * 因为 ExecAggTransReparent() 会确保在 NULL 时将 transValue 设置为 0。
	 * 否则我们可能会意外地不重新归属，当 transValue 的数值与 newValue 相同时，
	 * 尽管是 NULL。这是一条比较热门的路径，不希望为转换函数返回其
	 * （修改过的）输入参数的常见情况而引入另一条分支。
	 */
	if (DatumGetPointer(fc_newVal) != DatumGetPointer(fc_pergroup->transValue))
		fc_newVal = ExecAggTransReparent(fc_aggstate, fc_pertrans,
									  fc_newVal, fcinfo->isnull,
									  fc_pergroup->transValue,
									  fc_pergroup->transValueIsNull);

	fc_pergroup->transValue = fc_newVal;
	fc_pergroup->transValueIsNull = fcinfo->isnull;

	MemoryContextSwitchTo(fc_oldContext);
}
